一、题目描述

二、解题思路
使用前缀和 + 哈希表的方法来高效解决这个问题:
-
计算到每个位置的前缀和(当前位置之前所有元素的和)
-
对于当前前缀和 presum(i),如果存在某个之前的前缀和presum(j),满足presum(i)- presum(j)= k, 那么从那个位置到当前位置的子数组和就是k
-
变换一下公式,得到presum(i)- k= presum(j),我们发现只要有一个 在此之前的前缀和等于当前前缀和减去k,那么就有一个符合的要求的字串
-
重要思想:使用哈希表,k记录前缀和,v记录每个前缀和出现的次数
-
注意:初始化时,前缀和为0出现了1次(表示数组开始前的情况)
三、完整代码
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp; // 哈希表:key=前缀和, value=出现次数
mp[0] = 1; // 前缀和为0出现1次
int sum = 0; // 当前前缀和
int count = 0; // 符合条件的子数组个数
for(int i = 0; i < size(nums); i++) {
sum = sum + nums[i]; // 更新前缀和
if(mp.count(sum - k)) { // 检查是否存在sum-k的前缀和
count = count + mp[sum - k]; // 加上出现的次数
}
mp[sum]++; // 更新当前前缀和的出现次数
}
return count;
}
};
四、代码解析
1. 哈希表初始化
map<int, int> mp;
mp[0] = 1;
-
关键:map[0]=1表示前缀和为0的情况出现了1次
-
情况1:对于从数组开头开始的子数组等于k的情况
- k=3 nums=【2,1,4,-1】
-
情况2:对于子串元素数为1的情况
- k=3,nums=【1,4,-2,3,6】
2. 前缀和计算
sum = sum + nums[i];
-
累加当前元素,得到从数组开头到当前位置的总和
-
这个值表示前 i+1 个元素的和
3. 查找符合条件的子数组
if(mp.count(sum - k)) {
count = count + mp[sum - k];
}
-
核心逻辑:如果存在之前的前缀和等于 sum - k (当前的前缀和-目标k)
-
那么相减获得的子数组和就是 k
-
可能有多个位置的前缀和相同,所以要加上出现的次数
4. 更新哈希表
mp[sum]++;
-
将当前前缀和的出现次数加1
-
这个操作要在检查之后进行,避免使用当前自身的前缀和
五、语法要点
1. 为什么不先整体计算前缀和再处理?
如果先建完整的哈希表,在统计时无法区分前缀和是在当前位置之前还是之后。
2. 为什么使用 map<前缀和, 频次> 而不是 map<索引, 前缀和>?
(1)快速查找需求:
-
我们需要快速知道:是否存在某个前缀和等于
当前前缀和 - k -
使用
map<前缀和, 频次>:mp.count(sum - k)是 O(1) 查找 -
使用
map<索引, 前缀和>:需要遍历所有索引才能找到特定前缀和,效率低
(2)计数需求:
-
使用频次可以准确统计所有可能性
-
例如:
nums = [1, -1, 1, -1, 1],前缀和为1的位置可能有多个 -
同一前缀和可能出现在多个位置(如数组中有0或正负抵消的情况)
六、执行示例
输入:nums = [1,1,1], k = 2
执行过程:
-
初始化:
mp = {0:1},sum=0,count=0 -
i=0:
sum=1,检查1-2=-1不存在 →mp[1]=1→mp={0:1, 1:1} -
i=1:
sum=2,检查2-2=0存在 →count+=mp[0]=1→mp[2]=1→mp={0:1, 1:1, 2:1} -
i=2:
sum=3,检查3-2=1存在 →count+=mp[1]=2→mp[3]=1→mp={0:1, 1:1, 2:1, 3:1}
最终结果:2
对应子数组:
-
索引 [0,1]:nums[0]+nums[1] = 1+1 = 2
-
索引 [1,2]:nums[1]+nums[2] = 1+1 = 2
七、总结
本文介绍了使用前缀和和哈希表统计和为k的子数组个数的高效算法。该算法的核心在于将问题转化为寻找两个前缀和之差等于k的情况。通过维护一个哈希表记录每个前缀和出现的次数,我们可以在O(n)时间复杂度内解决问题。这种方法特别适用于包含负数的数组,而滑动窗口法只适用于全正数的情况。关键点包括:正确初始化mp[0]=1、理解sum-k的含义、以及及时更新前缀和的出现次数。