摘要:理解前缀和与哈希表的应用技巧。如果只使用前缀和,枚举区间长度还需要平方级别的时间复杂度。本题介绍的方法可以通过一次遍历,在遍历的过程中记住一些信息。
325. 和等于 k 的最长子数组长度
给定一个数组 nums 和一个目标值 k,找到和等于 k 的最长子数组长度。如果不存在任意一个符合要求的子数组,则返回 0。
注意:
nums 数组的总和是一定在 32 位有符号整数范围之内的。
示例 1:
输入: nums = [1, -1, 5, -2, 3], k = 3
输出: 4
解释: 子数组 [1, -1, 5, -2] 和等于 3,且长度最长。
示例 2:
输入: nums = [-2, -1, 2, 1], k = 1
输出: 2
解释: 子数组 [-1, 2] 和等于 1,且长度最长。
进阶: 你能使时间复杂度在 O(n) 内完成此题吗?
方法一:通过前缀和求区间和(超时)
public class Solution {
// 通过前缀和得到区间和:超时
public int maxSubArrayLen(int[] nums, int k) {
int len = nums.length;
int[] preSum = new int[len + 1];
for (int i = 0; i < len; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
int res = 0;
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
// 区间和
int sum = preSum[j + 1] - preSum[i];
if (sum == k) {
res = Math.max(res, j - i + 1);
}
}
}
return res;
}
}
方法二:前缀和与哈希表
关键字:连续子数组,连续很重要。
我们发现,通过前缀和求区间和(空间换时间),还需要枚举所有的区间,时间复杂度 。
是不是可以一次遍历呢?事实上是可以的,继续「空间换时间」,在求出前缀和的时候,记住以前遍历过的前缀和的信息。根据
前缀和_j + 区间和 k = 前缀和_i
把「区间和 k」从左边移到右边,得到:
前缀和_j = 前缀和_i - 区间和 k
因此我们在遍历到一个新的数 nums[i] 的时候,可以计算出已经遍历到的元素的前缀和 preSum,进而判断 preSum - k 是不是在哈希表中。
然后把「键值对」
key:preSum ,value:i
存入哈希表。是不是和「两数之和」的思想差不多?关键的地方写在代码注释里了。
注意(很重要)
由于要找的是「最长的」 连续 子数组,我们只记录前缀和第 1 次出现的下标。
参考代码:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int maxSubArrayLen(int[] nums, int k) {
int len = nums.length;
Map<Integer, Integer> hashMap = new HashMap<>(len);
// 计算长度的时候,经常要加上这个哨兵
hashMap.put(0, -1);
int res = 0;
int preSum = 0;
for (int i = 0; i < len; i++) {
preSum += nums[i];
if (hashMap.containsKey(preSum - k)) {
res = Math.max(res, i - hashMap.get(preSum - k));
}
// 注意:由于要找最长的,因此只记录第 1 次出现的下标
if (!hashMap.containsKey(preSum)) {
hashMap.put(preSum, i);
}
}
return res;
}
}
理解下标之间相减等于区间的长度: