题目理解
给定整数数组nums和整数k,统计连续非空 子数组中和为k的个数。
- 示例 1:
nums = [1,1], k=2→ 输出 2([1,1]、[1(第二个)]) - 示例 2:
nums = [1,2,3], k=3→ 输出 2([1,2]、[3])
解法 1:暴力前缀和(超时预警)
首先想到前缀和数组的基础思路:
- 构造前缀和数组
v,其中v[i]表示nums[0..i-1]的和(v[0]=0,v[1]=nums[0],以此类推) - 任意子数组
nums[j..i-1]的和 =v[i] - v[j],遍历所有i>j,统计v[i]-v[j]==k的次数
代码如下:
cpp
运行
int subarraySum(vector<int>& nums, int k) {
vector<int> v(nums.size()+1);
int count=0;
// 构造前缀和数组
for(int i=1;i<=nums.size();++i) {
v[i] = v[i-1] + nums[i-1];
}
// 遍历所有i>j的组合
for(int i=1;i<=nums.size();++i) {
for(int j=0;j<i;++j) {
if(v[i]-v[j] == k) count++;
}
}
return count;
}
但这个方法的时间复杂度是O(n²),对于n=2e4的用例来说会超时,必须优化。
解法 2:前缀和 + 哈希表(O (n) 时间)
核心思路是用哈希表记录前缀和出现的次数,避免二次遍历:
- 维护一个变量
sum,表示当前遍历到的前缀和(代替前缀和数组) - 用哈希表
hash存储前缀和的值 → 出现的次数 ,初始化hash[{0,1}](对应前缀和为 0 的情况,防止第一个元素就是 k 的情况) - 遍历数组时:
- 累加
sum得到当前前缀和 - 若
sum - k在哈希表中存在,说明存在前缀和为sum-k的子数组,其到当前位置的子数组和为 k,将次数累加到结果 - 将当前前缀和
sum的出现次数存入哈希表
- 累加
优化后的代码:
cpp
运行
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> hash = {{0, 1}}; // 初始化前缀和0出现1次
int ret = 0, sum = 0;
for (int e : nums) {
sum += e; // 当前前缀和
// 统计有多少个前缀和等于sum-k
if (hash.count(sum - k)) {
ret += hash[sum - k];
}
hash[sum]++; // 记录当前前缀和的出现次数
}
return ret;
}
为什么这样能行?
举个例子(nums=[1,1],k=2):
- 初始化:
sum=0,hash={0:1} - 遍历第一个 1:
sum=1,sum-k=-1不在 hash 中 →hash[1]设为 1 - 遍历第二个 1:
sum=2,sum-k=0在 hash 中(次数 1)→ret=1,再将hash[2]设为 1 - 最终
ret=2,和示例结果一致
总结
- 暴力前缀和:时间
O(n²),空间O(n)(超时) - 前缀和 + 哈希表:时间
O(n),空间O(n)(最优解)