给你一个下标从 0 开始的整数数组 nums
和一个正整数 k
。
你可以对数组执行下述操作 任意次 :
- 从数组中选出长度为
k
的 任一 子数组,并将子数组中每个元素都 减去1
。
如果你可以使数组中的所有元素都等于 0
,返回 true
;否则,返回 false
。
子数组 是数组中的一个非空连续元素序列。
示例 1:
输入:nums = [2,2,3,1,1,0], k = 3 输出:true 解释:可以执行下述操作: - 选出子数组 [2,2,3] ,执行操作后,数组变为 nums = [1,1,2,1,1,0] 。 - 选出子数组 [2,1,1] ,执行操作后,数组变为 nums = [1,1,1,0,0,0] 。 - 选出子数组 [1,1,1] ,执行操作后,数组变为 nums = [0,0,0,0,0,0] 。
示例 2:
输入:nums = [1,3,1,1], k = 2 输出:false 解释:无法使数组中的所有元素等于 0 。
提示:
1 <= k <= nums.length <= 10^5
0 <= nums[i] <= 10^6
提示 1
In case it is possible, then how can you do the operations? which subarrays do you choose and in what order?
提示 2
The order of the chosen subarrays should be from the left to the right of the array
解法:差分
提示 1
想一想,如果 nums[0]>0,我们必须要执行什么样的操作,才能让 nums[0]=0?
提示 2
对于 nums[0]>0 的情况,必须把 nums[0] 到 nums[k−1] 都减去 nums[0]。
然后思考 nums[1] 要怎么处理,依次类推。
提示 3
子数组同时加上/减去一个数,非常适合用 差分数组 来维护,请至少做一道差分数组题目再往下阅读。
关于对差分数组的详细讲解:LeetCode 1094. 拼车-CSDN博客
与本题类似题型:LeetCode 2528. 最大化城市的最小电量-CSDN博客
其他差分数组的题型:
LeetCode 2406. 将区间分为最少组数-CSDN博客
设差分数组为 diff。那么把 nums[i] 到 nums[i + k − 1] 同时减去 1,等价于把 diff[i] 减 1,diff[i+k] 加 1。
注意子数组长度必须恰好等于 k,所以当 i+k≤n 时,才能执行上述操作。
diff 数组长度设为n,当 i + k == n时,i 到 i + k - 1是最后一个长度为 k 的子数组,再往后没有了,所以 diff [i + k] 不用记录了,所以此时diff[i+k] 长度刚好超出范围也没关系。
遍历数组的同时,用变量 sumD 累加差分值(sumD是负数或0,代表到目前下标num[i]累积应变化的值)。遍历到 nums[i] 时,nums[i]+sumD 就是 nums[i] 的实际值了。
分类讨论:
- 如果 nums[i]<0,由于无法让元素值增大,返回 false。
- 如果 nums[i]=0,无需操作,遍历下一个数。
- 如果 nums[i]>0:
- 如果 i+k>n,无法执行操作,所以 nums[i] 无法变成 0,返回 false。
- 如果 i+k≤n,按照上面说的执行操作,修改差分数组,遍历下一个数。
- 如果遍历中途没有返回 false,那么最后返回 true。
sumD 是差分数组里面的一个进阶技巧,它可以做到一次遍历的时候就能同时更新nums,diff(差分数组)。所以可以做到一次循环解决这一题。
因为它会在一开始的时候先把当前位置的差分数组值加到自己身上 sumD += diff[i];
然后我们会在用 nums[i] += sumD
去更新 nums[i] 的大小,之后就能根据 nums[i] 配合题目去做相对应的事情。
在本题中间的 if 判断式都是类似剪枝的操作。
大概逻辑顺序是:
- 初始化差分数组
- 遍历nums:
- 2.1 更新sumD -> sumD += diff[i]
- 2.2 更新nums[i] -> nums[i] += sumD
- 2.3 根据条件更新diff[i] 和 diff[i + k]
- 初始化差分数组 目的:为了能够高效地表示和处理对原数组nums的连续区间修改操作。 操作:创建一个差分数组diff。初始时,差分数组diff的所有元素都设置为0。
- 遍历nums 接下来的步骤在遍历原数组nums的过程中进行:
- 2.1 更新sumD 操作:sumD += diff[i]。 目的:累积到当前位置为止,所有之前操作的累计影响。sumD在此充当一个"运行总和",反映了从数组开始到当前位置的所有差分值的累加效果。
- 2.2 更新nums[i] 操作:nums[i] += sumD。 目的:应用到目前为止所有累积的差分操作对当前元素nums[i]的影响。这一步实际上是在动态地还原原数组,考虑到所有之前的区间修改。
- 2.3 根据条件更新diff[i]和diff[i + k]操作: 如果nums[i](更新后)需要进一步的操作来变为0(例如,nums[i]大于0),则需要在差分数组diff上做出相应的修改以记录这一操作。diff[i + k] += nums[i](或者是调整值的相应操作)。 目的:调整差分数组以表示从当前位置 i 开始,长度为 k 的子数组将被减少的操作。这是为了确保后续遍历时能够正确反映所有已经执行的操作的累积效果。
Java版:
class Solution {
public boolean checkArray(int[] nums, int k) {
int n = nums.length;
int[] diff = new int[n];
int sumD = 0;
for (int i = 0; i < n; i++) {
sumD += diff[i];
nums[i] += sumD;
if (nums[i] == 0) {
continue;
}
if (nums[i] < 0 || i + k > n) {
return false;
}
// 此时nums[i] > 0,将其变为0,需要num[i]到nums[i + k - 1]同时减去nums[i]
sumD -= nums[i];
if (i + k < n) {
diff[i + k] += nums[i];
}
}
return true;
}
}
Python版:
class Solution:
def checkArray(self, nums: List[int], k: int) -> bool:
n = len(nums)
diff = [0] * n
sumD = 0
for i in range(n):
sumD += diff[i]
nums[i] += sumD
if nums[i] == 0:
continue
if nums[i] < 0 or i + k > n:
return False
sumD -= nums[i]
if i + k < n:
diff[i + k] += nums[i]
return True
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(n),即为差分数组 diff 需要使用的空间。
另一种写法:
Java版:
class Solution {
public boolean checkArray(int[] nums, int k) {
int n = nums.length;
int[] diff = new int[n + 1];
int sumd = 0;
for (int i = 0; i < n; i++) {
sumd += diff[i];
nums[i] += sumd;
if (nums[i] == 0) {
continue;
} else if (nums[i] < 0) {
return false;
} else {
if (i + k > n) {
return false;
}
diff[i + k] += nums[i];
sumd -= nums[i];
}
}
return true;
}
}
Python3版:
class Solution:
def checkArray(self, nums: List[int], k: int) -> bool:
n = len(nums)
diff = [0] * (n + 1)
sumd = 0
for i in range(n):
sumd += diff[i]
nums[i] += sumd
if nums[i] == 0:
continue
elif nums[i] < 0:
return False
else:
if i + k > n:
return False
sumd -= nums[i]
diff[i + k] += nums[i]
return True