【前缀和】LeetCode 523. 连续的子数组和
题目链接
思路
参考宫水三叶大佬题解
一开始以为和 Leetcode 53 Maximum Subarray 思路差不多,都是求子数组的值。但是后来发现在53题中并没有求出每个子数组的和,只是在贪心的情况下求出了可能的最大和
假设一段子数组的和是 subSum
,那么如果要使得这段子数组符合要求的话,需要有
\[subSum = n * k
\]
那么该如何求一段子数组的和呢?答案就是前缀和,它只需要使用子数组的首尾两端相减便能求出对应的子数组和
假设 \([i, j]\) 是我们的子数组区间
\[subSum = sum[j] - sum[i - 1]
\]
公式里是 \(i - 1\) 而不是 \(i\) 是因为前缀和数组 sum[i]
表示到下标为 \(i\) 的和,而我们要求的和是 \([i, j]\) 的
所以最终能得到公式
\[sum[j] - sum[i - 1] = n * k
\]
整理得
\[\frac{sum[j]}{k} - \frac{sum[i - 1]}{k} = n
\]
显然题目要求 \(n\) 为整数,那么就需要思考在什么情况下 \(n\) 为整数。
显然就是 sum[j] % k == sum[i - 1] % k
的时候 \(n\) 是整数
也就是说,我们只需要枚举右端点 \(j\),然后在枚举右端点 \(j\) 的时候检查之前是否出现过左端点 \(i\),使得 \(sum[j]\) 和 \(sum[i−1]\) 对 \(k\) 取余相同。
这样我们就把最初的查找是否有符合条件的子数组问题转化为了查找是否有符合条件的前缀和,问题简化了不少。
具体的,使用 HashSet
来保存出现过的值。
让循环从 2 开始枚举右端点(根据题目要求,子数组长度至少为 2),每次将符合长度要求的位置的取余结果存入 HashSet
。
如果枚举某个右端点 \(j\) 时发现存在某个左端点 \(i\) 符合要求,则返回 true
。
代码
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int n = nums.length;
int[] sum = new int[n + 1];
for(int i = 1; i <= n; i++){
sum[i] = sum[i - 1] + nums[i - 1];
}
Set<Integer> set = new HashSet<>();
for(int i = 2; i <= n; i++){
set.add(sum[i - 2] % k);
if(set.contains(sum[i] % k)){
return true;
}
}
return false;
}
}