摘要:求区间和经常需要想到前缀和,前缀和需要预处理。把预处理的信息记录到哈希表,这是常见的做法。在写代码的时候需要比较小心,弄清楚「循环不变量」。
题解 | 「力扣」第 523 题:连续数组(中等)
给定一个包含 非负数 的数组和一个目标 整数 k ,编写一个函数来判断该数组是否含有连续的子数组,其 大小至少为 ,且总和为 k 的倍数,即总和为 n * k ,其中 n 也是一个整数。
示例 1:
输入:[23, 2, 4, 6, 7], k = 6
输出:True
解释:[2, 4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:
输入:[23, 2, 6, 4, 7], k = 6
输出:True
解释:[23, 2, 6, 4, 7]是大小为 5 的子数组,并且和为 42。
说明:
思路分析
- 先考虑暴力解法,优化的思路是「空间换时间」;
- 连续 子区间的问题很多时候可以考虑使用「前缀和」的思想;
- 一边遍历、一边执行相关操作,因此可以考虑使用哈希表(这是一条经验)。
方法一:暴力解法(超时)
枚举所有长度 大于等于 的连续子数组,对它们分别求和,并判断和是否是给定整数 的倍数。
参考代码 1:
public class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int len = nums.length;
for (int left = 0; left < len - 1; left++) {
// 大小至少为 2
for (int right = left + 1; right < len; right++) {
int sum = 0;
for (int i = left; i <= right; i++) {
sum += nums[i];
}
if (sum == k || (k != 0 && sum % k == 0)) {
return true;
}
}
}
return false;
}
}
时间复杂度: ,这里 是输入数组的长度,使用了三重嵌套的 for 循环遍历数组,因此时间复杂度是 。
只用了常数个额外变量,因此空间复杂度是 。
方法二:通过前缀和计算区间和(超时)
「前缀和」的基本思想是 空间换时间:
- 注意到题目中我们求连续子数组的区间和;
- 求区间和的一个基本的技巧是:根据前缀和的差,求出区间和。
参考代码 2:
public class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int len = nums.length;
// preSum[i] 表示:区间 nums[0..i) 的前缀和
int[] preSum = new int[len + 1];
preSum[0] = 0;
for (int i = 0; i < len; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
for (int left = 0; left < len - 1; left++) {
// 大小至少为 2
for (int right = left + 1; right < len; right++) {
// 区间和
int sum = preSum[right + 1] - preSum[left];
if (sum == k || (k != 0 && sum % k == 0)) {
return true;
}
}
}
return false;
}
}
时间复杂度: ,这里 是输入数组的长度,构建前缀和数组 ,枚举所有长度大于等于 的连续子数组 。
重点
根据求解 「力扣」第 1 题(两数之和)的经验,我们可以在遍历的过程当中记录已经出现的信息,这样就可以通过一次遍历完成计算。记录已经遍历过的信息使用 哈希表。
前缀和对 的余数相同,说明区间和是 的倍数。
方法三:前缀和与哈希表
在遍历的时候哈希表中,向哈希表插入记录:
key:sum % k
value:i
参考代码 3:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int sum = 0;
// key:区间 [0..i] 里所有元素的和 % k
// value:下标 i
Map<Integer, Integer> map = new HashMap<>();
// 理解初始化的意义,-1 是下标,可以认为是哨兵
map.put(0, -1);
int len = nums.length;
for (int i = 0; i < len; i++) {
sum = (sum + nums[i]) % k;
if (map.containsKey(sum)) {
// 长度大于 2
if (i - map.get(sum) > 1) {
return true;
}
} else {
map.put(sum, i);
}
}
return false;
}
}
时间复杂度:,仅需要遍历输入数组一遍。
思考
这道问题的循环不变量是什么?