给你一个整数数组 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
这是 一道经典的前缀和 + 哈希表 应用题,考察如何快速找出 和为某个目标值的子数组个数。
🧠 解题思路(适用于初学者)
我们要找的是:数组中 和为 k
的连续子数组个数。
🔍 暴力解法(不推荐)
- 枚举所有可能的子数组,计算每个子数组的和,看是否等于 k。
- 时间复杂度:O(n²)
- 会超时 ❌
✅ 高效做法:前缀和 + 哈希表(推荐)
核心思想:
如果两个前缀和的差为
k
,那么它们之间的子数组和就是k
。
假设:
- 当前前缀和是
pre_sum
- 我们想找到某个之前的前缀和
pre_sum - k
- 如果哈希表中有这个
pre_sum - k
出现过,那就说明存在一个子数组的和是 k!
✅ Python 解法
def subarraySum(nums, k):
from collections import defaultdict
pre_sum_count = defaultdict(int)
pre_sum_count[0] = 1 # 初始化,表示前缀和为0出现1次
pre_sum = 0
count = 0
for num in nums:
pre_sum += num
# 如果 pre_sum - k 存在,说明有一个子数组和为k
count += pre_sum_count.get(pre_sum - k, 0)
# 更新哈希表中当前前缀和的次数
pre_sum_count[pre_sum] += 1
return count
✅ Java 解法
import java.util.HashMap;
public class Solution {
public int subarraySum(int[] nums, int k) {
// key: 前缀和, value: 出现次数
HashMap<Integer, Integer> preSumCount = new HashMap<>();
preSumCount.put(0, 1); // 初始化:前缀和为0出现1次
int preSum = 0;
int count = 0;
for (int num : nums) {
preSum += num;
// 检查是否存在 preSum - k
if (preSumCount.containsKey(preSum - k)) {
count += preSumCount.get(preSum - k);
}
// 更新当前前缀和的出现次数
preSumCount.put(preSum, preSumCount.getOrDefault(preSum, 0) + 1);
}
return count;
}
}
✅ 示例解释
以 nums = [1, 1, 1], k = 2
为例:
前缀和序列为:[1, 2, 3]
- 子数组 [1,1](下标0~1),和为2
- 子数组 [1,1](下标1~2),和为2
所以输出是 2
📌 小结
技术点 | 解释 |
---|---|
前缀和 | 快速计算区间和 |
哈希表 | 存储前缀和出现次数 |
时间复杂度 | O(n) |
空间复杂度 | O(n) |
🌟需要理解的三个关键词:
1. 前缀和(prefix sum)
- 到当前位置为止,所有元素的累加和。
比如:数组 [1, 2, 3]
的前缀和依次是:
- 第0个元素:
1
- 第1个元素:
1 + 2 = 3
- 第2个元素:
1 + 2 + 3 = 6
2. 目标:找出所有和为 k 的连续子数组
我们希望找到某个区间 i ~ j
,它的和等于 k
。
设:
pre_sum[j] = 前 j 项的和
pre_sum[i-1] = 前 i-1 项的和
那么:
pre_sum[j] - pre_sum[i-1] = k
也就是:
pre_sum[i-1] = pre_sum[j] - k
如果我们记录了所有的 前缀和
,就可以通过查找 pre_sum - k
是否存在,来判断是否有某段子数组的和为 k。
3. 哈希表记录前缀和的出现次数
pre_sum_count[前缀和] = 出现的次数
✅ 现在来看你问的这句代码:
count += pre_sum_count.get(pre_sum - k, 0)
意思是:
当前累加的前缀和是 pre_sum
,
我们想找有没有 之前出现过 的某个 pre_sum - k
。
- 如果有,那说明从那个位置之后到当前位置这段区间的和是
k
- 它出现几次,就代表有几个子数组满足要求
🔍 举个完整例子:
nums = [1, 2, 1, 1], k = 3
依次计算前缀和,并用字典记录每个前缀和出现的次数:
步骤 | num | pre_sum | pre_sum - k | pre_sum_count[pre_sum - k] | count |
---|---|---|---|---|---|
0 | 1 | 1 | -2 | 0 | 0 |
1 | 2 | 3 | 0 | 1 ←初始 pre_sum=0 | 1 |
2 | 1 | 4 | 1 | 1 | 2 |
3 | 1 | 5 | 2 | 0 | 2 |
最终答案是 2
🧾总结:
count += pre_sum_count.get(pre_sum - k, 0)
👉 就是问:
有没有出现过
pre_sum - k
这个前缀和?出现了几次?
- 有几次,就有几个子数组以当前结尾,和为
k
- 所以要加上这个次数