- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问.
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅.
本文是 LeetCode 上分之旅系列的第 44 篇文章,往期回顾请移步到文章末尾~ 。
T1. 统计对称整数的数目(Easy) 。
T2. 生成特殊数字的最少操作(Medium) 。
T3. 统计趣味子数组的数目(Medium) 。
T4. 边权重均等查询(Hard) 。
https://leetcode.cn/problems/count-symmetric-integers/
根据题意模拟,亦可以使用前缀和预处理优化.
class Solution {
fun countSymmetricIntegers(low: Int, high: Int): Int {
var ret = 0
for (x in low..high) {
val s = "$x"
val n = s.length
if (n % 2 != 0) continue
var diff = 0
for (i in 0 until n / 2) {
diff += s[i] - '0'
diff -= s[n - 1 - i] - '0'
}
if (diff == 0) ret += 1
}
return ret
}
}
复杂度分析:
https://leetcode.cn/problems/minimum-operations-to-make-a-special-number/
思维题,这道卡了多少人.
可以用回溯解决:
class Solution {
fun minimumOperations(num: String): Int {
val memo = HashMap<String, Int>()
fun count(x: String): Int {
val n = x.length
if (n == 1) return if (x == "0") 0 else 1
if (((x[n - 2] - '0') * 10 + (x[n - 1]- '0')) % 25 == 0) return 0
if(memo.containsKey(x))return memo[x]!!
val builder1 = StringBuilder(x)
builder1.deleteCharAt(n - 1)
val builder2 = StringBuilder(x)
builder2.deleteCharAt(n - 2)
val ret = 1 + min(count(builder1.toString()), count(builder2.toString()))
memo[x]=ret
return ret
}
return count(num)
}
}
复杂度分析:
初步分析:
具体实现:
class Solution {
fun minimumOperations(num: String): Int {
val n = num.length
var ret = n
for (choice in arrayOf("00", "25", "50", "75")) {
// 双指针
var j = 1
for (i in n - 1 downTo 0) {
if (choice[j] != num[i]) continue
if (--j == -1) {
ret = min(ret, n - i - 2)
break
}
}
}
// 特殊情况
ret = min(ret, n - num.count { it == '0'})
return ret
}
}
复杂度分析:
https://leetcode.cn/problems/count-of-interesting-subarrays/
初步分析:
分析到这里,容易想到用前缀和实现:
组合以上技巧:
class Solution {
fun countInterestingSubarrays(nums: List<Int>, m: Int, k: Int): Long {
val n = nums.size
var ret = 0L
val preSum = HashMap<Int, Int>()
preSum[0] = 1 // 注意空数组的状态
var cur = 0
for (i in 0 until n) {
if (nums[i] % m == k) cur ++ // 更新前缀和
val key = cur % m
val target = (key - k + m) % m
ret += preSum.getOrDefault(target, 0) // 记录方案
preSum[key] = preSum.getOrDefault(key, 0) + 1 // 记录前缀和
}
return ret
}
}
复杂度分析:
相似题目:
https://leetcode.cn/problems/minimum-edge-weight-equilibrium-queries-in-a-tree/
初步分析:
思考实现:
现在的关键问题是,如何快速地找到 $<x, y>$ 的最近公共祖先 LCA?
对于单次 LCA 操作来说,我们可以走 DFS 实现 $O(n)$ 时间复杂度的算法,而对于多次 LCA 操作可以使用 倍增算法 预处理以空间换时间,单次 LCA 操作的时间复杂度进位 $O(lgn)$.
在 LeetCode 有倍增的模板题 1483. 树节点的第 K 个祖先 .
在求 LCA 时,我们先把 $<x, y>$ 跳到相同高度,再利用倍增算法向上跳 $2^j$ 个父节点,直到到达相同节点即为最近公共祖先.
class Solution {
fun minOperationsQueries(n: Int, edges: Array<IntArray>, queries: Array<IntArray>): IntArray {
val U = 26
// 建图
val graph = Array(n) { LinkedList<IntArray>() }
for (edge in edges) {
graph[edge[0]].add(intArrayOf(edge[1], edge[2] - 1))
graph[edge[1]].add(intArrayOf(edge[0], edge[2] - 1))
}
// 预处理深度、倍增祖先节点、倍增路径信息
val m = 32 - Integer.numberOfLeadingZeros(n - 1)
val depth = IntArray(n)
val parent = Array(n) { IntArray(m) { -1 }} // parent[i][j] 表示 i 的第 2^j 个父节点
val cnt = Array(n) { Array(m) { IntArray(U) }} // cnt[i][j] 表示 <i - 2^j> 个父节点的路径信息
fun dfs(i: Int, par: Int) {
for ((to, w) in graph[i]) {
if (to == par) continue // 避免回环
depth[to] = depth[i] + 1
parent[to][0] = i
cnt[to][0][w] = 1
dfs(to, i)
}
}
dfs(0, -1) // 选择 0 作为根节点
// 预处理倍增
for (j in 1 until m) {
for (i in 0 until n) {
val from = parent[i][j - 1]
if (-1 != from) {
parent[i][j] = parent[from][j - 1]
cnt[i][j] = cnt[i][j - 1].zip(cnt[from][j - 1]) { e1, e2 -> e1 + e2 }.toIntArray()
}
}
}
// 查询
val q = queries.size
val ret = IntArray(q)
for ((i, query) in queries.withIndex()) {
var (x, y) = query
// 特判
if (x == y || parent[x][0] == y || parent[y][0] == x) {
ret[i] = 0
}
val w = IntArray(U) // 记录路径信息
var path = depth[x] + depth[y] // 记录路径长度
// 先跳到相同高度
if (depth[y] > depth[x]) {
val temp = x
x = y
y = temp
}
var k = depth[x] - depth[y]
while (k > 0) {
val j = Integer.numberOfTrailingZeros(k) // 二进制分解
w.indices.forEach { w[it] += cnt[x][j][it] } // 记录路径信息
x = parent[x][j] // 向上跳 2^j 个父节点
k = k and (k - 1)
}
// 再使用倍增找 LCA
if (x != y) {
for (j in m - 1 downTo 0) { // 最多跳 m - 1 次
if (parent[x][j] == parent[y][j]) continue // 跳上去相同就不跳
w.indices.forEach { w[it] += cnt[x][j][it] } // 记录路径信息
w.indices.forEach { w[it] += cnt[y][j][it] } // 记录路径信息
x = parent[x][j]
y = parent[y][j] // 向上跳 2^j 个父节点
}
// 最后再跳一次就是 lca
w.indices.forEach { w[it] += cnt[x][0][it] } // 记录路径信息
w.indices.forEach { w[it] += cnt[y][0][it] } // 记录路径信息
x = parent[x][0]
}
// 减去重链长度
ret[i] = path - 2 * depth[x] - w.max()
}
return ret
}
}
复杂度分析:
推荐阅读
LeetCode 上分之旅系列往期回顾:
- LeetCode 单周赛第 360 场 · 当 LeetCode 考树上倍增,出题的趋势在变化吗
- LeetCode 单周赛第 359 场 · 结合离散化的线性 DP 问题
- LeetCode 双周赛第 112 场 · 计算机科学本质上是数学吗?
- LeetCode 双周赛第 111 场 · 按部就班地解决动态规划问题
⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~ 。
最后此篇关于LeetCode周赛上分之旅#44同余前缀和问题与经典倍增LCA算法的文章就讲到这里了,如果你想了解更多关于LeetCode周赛上分之旅#44同余前缀和问题与经典倍增LCA算法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
滑动窗口限流 滑动窗口限流是一种常用的限流算法,通过维护一个固定大小的窗口,在单位时间内允许通过的请求次数不超过设定的阈值。具体来说,滑动窗口限流算法通常包括以下几个步骤: 初始化:设置窗口
表达式求值:一个只有+,-,*,/的表达式,没有括号 一种神奇的做法:使用数组存储数字和运算符,先把优先级别高的乘法和除法计算出来,再计算加法和减法 int GetVal(string s){
【算法】前缀和 题目 先来看一道题目:(前缀和模板题) 已知一个数组A[],现在想要求出其中一些数字的和。 输入格式: 先是整数N,M,表示一共有N个数字,有M组询问 接下来有N个数,表示A[1]..
1.前序遍历 根-左-右的顺序遍历,可以使用递归 void preOrder(Node *u){ if(u==NULL)return; printf("%d ",u->val);
先看题目 物品不能分隔,必须全部取走或者留下,因此称为01背包 (只有不取和取两种状态) 看第一个样例 我们需要把4个物品装入一个容量为10的背包 我们可以简化问题,从小到大入手分析 weightva
我最近在一次采访中遇到了这个问题: 给出以下矩阵: [[ R R R R R R], [ R B B B R R], [ B R R R B B], [ R B R R R R]] 找出是否有任
我正在尝试通过 C++ 算法从我的 outlook 帐户发送一封电子邮件,该帐户已经打开并记录,但真的不知道从哪里开始(对于 outlook-c++ 集成),谷歌也没有帮我这么多。任何提示将不胜感激。
我发现自己像这样编写了一个手工制作的 while 循环: std::list foo; // In my case, map, but list is simpler auto currentPoin
我有用于检测正方形的 opencv 代码。现在我想在检测正方形后,代码运行另一个命令。 代码如下: #include "cv.h" #include "cxcore.h" #include "high
我正在尝试模拟一个 matlab 函数“imfill”来填充二进制图像(1 和 0 的二维矩阵)。 我想在矩阵中指定一个起点,并像 imfill 的 4 连接版本那样进行洪水填充。 这是否已经存在于
我正在阅读 Robert Sedgewick 的《C++ 算法》。 Basic recurrences section it was mentioned as 这种循环出现在循环输入以消除一个项目的递
我正在思考如何在我的日历中生成代表任务的数据结构(仅供我个人使用)。我有来自 DBMS 的按日期排序的任务记录,如下所示: 买牛奶(18.1.2013) 任务日期 (2013-01-15) 任务标签(
输入一个未排序的整数数组A[1..n]只有 O(d) :(d int) 计算每个元素在单次迭代中出现在列表中的次数。 map 是balanced Binary Search Tree基于确保 O(nl
我遇到了一个问题,但我仍然不知道如何解决。我想出了如何用蛮力的方式来做到这一点,但是当有成千上万的元素时它就不起作用了。 Problem: Say you are given the followin
我有一个列表列表。 L1= [[...][...][.......].......]如果我在展平列表后获取所有元素并从中提取唯一值,那么我会得到一个列表 L2。我有另一个列表 L3,它是 L2 的某个
我们得到二维矩阵数组(假设长度为 i 和宽度为 j)和整数 k我们必须找到包含这个或更大总和的最小矩形的大小F.e k=7 4 1 1 1 1 1 4 4 Anwser是2,因为4+4=8 >= 7,
我实行 3 类倒制,每周换类。顺序为早类 (m)、晚类 (n) 和下午类 (a)。我固定的订单,即它永远不会改变,即使那个星期不工作也是如此。 我创建了一个函数来获取 ISO 周数。当我给它一个日期时
假设我们有一个输入,它是一个元素列表: {a, b, c, d, e, f} 还有不同的集合,可能包含这些元素的任意组合,也可能包含不在输入列表中的其他元素: A:{e,f} B:{d,f,a} C:
我有一个子集算法,可以找到给定集合的所有子集。原始集合的问题在于它是一个不断增长的集合,如果向其中添加元素,我需要再次重新计算它的子集。 有没有一种方法可以优化子集算法,该算法可以从最后一个计算点重新
我有一个包含 100 万个符号及其预期频率的表格。 我想通过为每个符号分配一个唯一(且前缀唯一)的可变长度位串来压缩这些符号的序列,然后将它们连接在一起以表示序列。 我想分配这些位串,以使编码序列的预
我是一名优秀的程序员,十分优秀!