【LeetCode 每日一题】3495. 使数组元素都变为零的最少操作次数
Problem: 3495. 使数组元素都变为零的最少操作次数
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(Q * log R_max)
- 空间复杂度:O(1)
整体思路
这段代码旨在高效地处理一系列查询 queries
。对于每个查询 [l, r]
,它需要计算一个特定的值,然后将所有查询的结果累加起来。问题的核心在于理解辅助函数 preSum(x)
的作用以及主函数中 Math.max((sum + 1) / 2, max)
这条计算公式的含义。
算法的整体思路可以分解为以下几个步骤:
-
定义“成本”函数
f(k)
:- 代码隐含地为每个正整数
k
定义了一个“成本”或“值”。这个成本由preSum
函数的内部逻辑决定。 preSum
函数将正整数1, 2, 3, ...
分成基于4的幂次的区间:[1, 3]
(即[4^0, 4^1 - 1]
)[4, 15]
(即[4^1, 4^2 - 1]
)[16, 63]
(即[4^2, 4^3 - 1]
)- …
- 第
i
个区间是[4^(i-1), 4^i - 1]
。
- 所有落在第
i
个区间的整数k
,其“成本”f(k)
都被定义为i
。
- 代码隐含地为每个正整数
-
高效计算“成本”的前缀和:
preSum(x)
函数的作用是计算从 1 到x
所有整数的“成本”之和,即Σ f(k)
fork
from 1 tox
。- 它通过
while
循环逐个处理上述的分层区间。在每次循环中,它计算出[1, x]
范围内有多少个数(count
)落在了当前处理的第i
个区间,然后将count * i
累加到总和ans
中。这种分层累加的方法远比逐个计算f(k)
要高效。
-
处理查询
[l, r]
:minOperations
方法遍历每个查询。对于一个查询[l, r]
:
a. 计算区间总成本sum
:利用前缀和的思想,区间[l, r]
的总成本可以通过preSum(r) - preSum(l - 1)
快速计算得出。
b. 计算区间最大成本max
:同样利用前缀和,区间内单个元素的最大成本必然是f(r)
,因为成本函数f(k)
是单调不减的。f(r)
的值可以通过preSum(r) - preSum(r - 1)
计算出来。
c. 应用特定公式:根据问题的具体要求(这通常与游戏理论或某种优化目标有关),将计算出的sum
和max
代入公式Math.max((sum + 1) / 2, max)
来得到该查询的结果。(sum + 1) / 2
是一种计算ceil(sum / 2.0)
的整数方法。
-
累加总结果:
- 将每个查询得到的结果累加到
ans
中,最终返回总和。
- 将每个查询得到的结果累加到
完整代码
class Solution {/*** 处理一系列查询,并返回结果的总和。* @param queries 一个二维数组,每个子数组 [l, r] 代表一个查询区间。* @return 所有查询结果的累加和。*/public long minOperations(int[][] queries) {long ans = 0;// 遍历每一个查询for (int[] query : queries) {int l = query[0];int r = query[1];// 使用 preSum 函数计算区间 [l, r] 内所有数字的“成本”总和。// 这是经典的前缀和用法。long sum = preSum(r) - preSum(l - 1);// 计算区间 [l, r] 内最大的单个“成本”。// 由于“成本”函数是单调不减的,所以最大成本就是 f(r)。long max = preSum(r) - preSum(r - 1);// 应用问题的特定公式来计算当前查询的结果。// (sum + 1) / 2 是 ceil(sum / 2.0) 的整数实现。ans += Math.max((sum + 1) / 2, max);}return ans;}/*** 计算从 1 到 x 的所有整数的“成本”之和。* “成本” f(k) = i,其中 4^(i-1) <= k < 4^i。* @param x 上限整数* @return 成本的前缀和*/private long preSum(int x) {// 如果 x 为 0 或负数,前缀和为 0if (x <= 0) return 0;long ans = 0; // 累加结果long p = 1; // 当前处理区间的起始值,p = 4^(i-1)int i = 1; // 当前处理区间的“成本”// 循环直到区间的起始值 p 超过 xwhile (p <= x) {// 计算 [1, x] 与当前成本区间 [p, 4*p - 1] 的交集中的元素数量。// Math.min(x, p * 4 - 1) 是交集的上界。long count = Math.min(x, p * 4 - 1) - p + 1;// 将这 count 个元素的总成本 (count * i) 累加到 ansans += count * i;// 移至下一个成本区间i++;p *= 4;}return ans;}
}
时空复杂度
时间复杂度:O(Q * log R_max)
-
preSum(x)
函数分析:- 该函数的核心是一个
while
循環。 - 循环变量
p
每次迭代都乘以 4 (1, 4, 16, 64, ...
)。这是一个指数级增长。 - 因此,循环的次数与
log4(x)
成正比。 - 所以,
preSum(x)
函数的时间复杂度为 O(log x)。
- 该函数的核心是一个
-
minOperations
函数分析:- 该函数的主体是一个
for
循环,它遍历所有的查询。设查询的数量为Q
。 - 在每次循环中,它会调用
preSum
函数三次。 - 调用的参数最大为
r
。设所有查询中r
的最大值为R_max
。 - 那么,单次查询的处理时间为
3 * O(log R_max)
,即 O(log R_max)。
- 该函数的主体是一个
综合分析:
总的时间复杂度是 (查询数量) * (单次查询的时间),即 O(Q * log R_max)。
空间复杂度:O(1)
- 存储分析:
minOperations
函数和preSum
函数在执行过程中都只使用了少数几个基本类型的变量(如ans
,l
,r
,sum
,max
,p
,i
,count
)。- 这些变量的数量是固定的,不随查询数量
Q
或查询范围R_max
的大小而改变。
综合分析:
算法没有使用任何与输入规模成比例的额外数据结构。因此,其额外辅助空间复杂度为 O(1)。