(一)问题描述
560. 和为 K 的子数组 - 力扣(LeetCode)560. 和为 K 的子数组 - 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。子数组是数组中元素的连续非空序列。 示例 1:输入:nums = [1,1,1], k = 2输出:2示例 2:输入:nums = [1,2,3], k = 3输出:2 提示: * 1 <= nums.length <= 2 * 104 * -1000 <= nums[i] <= 1000 * -107 <= k <= 107https://leetcode.cn/problems/subarray-sum-equals-k/description/?envType=study-plan-v2&envId=top-100-liked
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2 输出:2示例 2:
输入:nums = [1,2,3], k = 3 输出:2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
(二)解决思路
1. 滑动窗口(退化为暴力解)
求数组或字符串的“连续非空序列”,很容易想到滑动窗口。乍一看和209.长度最小的子数组很像,区别在于这道题中的元素不都是整数(即加的数越多,和反而可能越小),并且要找到满足条件的多个子数组。这里我的思路和3. 无重复字符的最长子串类似:找以各个元素为开头的满足和为k的子数组。但是问题就在于,数组中存在负数,那么以同一个元素开头的满足和为k的子数组就可能有多个,因此窗口右边界需要遍历从左边界开始直至数组结束的每一个元素。此时该方法也就退化成了暴力解法。
public class Solution {
public int subarraySum(int[] nums, int k) {
int count = 0;
for (int left = 0; left < nums.length; ++left) {
int sum = 0;
for (int right = left; right < nums.length;++right) {
sum += nums[right];
if (sum == k) {
count++;
}
}
}
return count;
}
}
2. 前缀和+哈希表
我们定义 pre[i] 为 [0..i] 里所有数的和(即元素i的前缀和),则 pre[i] 可以由 pre[i−1] 递推而来,即:
pre[i]=pre[i−1]+nums[i]
那么"[j..i] 这个子数组和为 k"这个条件我们可以转化为
pre[i]−pre[j−1]==k
简单移项可得符合条件的下标 j 需要满足
pre[j−1]==pre[i]−k
所以我们考虑以 i 结尾的和为 k 的连续子数组个数时只要统计有多少个前缀和为 pre[i]−k 的 pre[j] 即可。建立哈希表 map,以和为键,出现次数为对应的值,记录 pre[i] 出现的次数,从左往右边更新 mp 边计算答案。
public class Solution {
public int subarraySum(int[] nums, int k) {
int count=0,pre=0;
HashMap<Integer,Integer> map = new HashMap<>();
//初始状态0,1
map.put(0,1);
for(int i=0;i<nums.length;i++){
//计算当前元素前缀和
pre+=nums[i];
//如果存在前缀和等于pre-k的元素,计数+1
if(map.containsKey(pre-k)){
count+=map.get(pre-k);
}
//将当前元素的前缀和放进map中
//如果map已有当前元素的前缀和,那么在原有值的基础上加一
//如果map没有当前元素的前缀和,那么将其加入到map中,记出现次数为1
map.put(pre,map.getOrDefault(pre,0)+1);
}
return count;
}
}
小细节
(1)为什么要加入初始状态key=0,value=1。在进行判断时,是看在当前元素i之前,有没有出现过某一个或多个元素j,它的前缀和pre[j]=pre[i]-1。那么对于第一个元素,它之前没有其他元素,相当于该元素之前的元素前缀和为0。如果这里没有添加过前缀和为0的初始状态,将无法处理第一个元素的值恰好等于k的情况。
(2)为什么 将当前前缀和放入map 要放在判断 是否有前缀和等于pre-k 之后。我们认为元素i是这个连续子序列的末尾元素,可以当成是我要判断把末尾元素i加进来之后,整个连续子序列的和会不会等于k。考虑的是元素i的前缀和和它之前[0...i-1]个元素的前缀和的关系,如果先把pre[i]加进来再做判断,元素i就被重复考虑了。