思路一:暴力解法
我们通过维护一个动态的滑动窗口来解决问题。滑动窗口的范围由两个指针控制,一个指针表示窗口的起始位置(左边界),另一个指针表示窗口的结束位置(右边界)。窗口内的元素和会随着窗口范围的变化而动态调整。
具体逻辑如下:
- 初始化窗口和:从窗口的左边界开始,逐步扩展右边界,计算窗口内的元素和。
- 判断窗口和:
- 如果窗口内的元素和等于目标值,则说明这个窗口满足要求,计数器
num
加 1,并尝试扩展窗口。 - 如果窗口和小于目标值,说明当前窗口还不够大,需要继续扩展右边界以包含更多元素。
- 如果窗口和大于目标值,说明窗口内的元素过多,需要缩小窗口,即移动左边界。不断调整窗口的范围,直到右边界遍历完整个数组。
最终,num
就是所有满足条件的子数组个数。
虽然可以编译成功,但是超出时间限制了
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int num = 0;//记录满足条件的个数
int n = nums.size();
for(int i = 0; i < n; i++){
int left = i;//动态窗口的左边界索引
int right = left;//动态窗口的左边界索引
int sum = 0;
while(right >= 0){
sum += nums[right];
if(sum == k){
num++;
}
right--;
}
}
return num;
}
};
思路二:哈希表法
遍历数组,计算当前位置的累积和(前缀和),并通过当前前缀和 - 目标值来判断是否存在满足条件的子数组。
为快速查找之前的前缀和,我们用哈希表记录每个前缀和出现的次数:
- 如果
当前前缀和 - 目标值
在哈希表中存在,说明有对应的子数组满足条件,将其出现次数加到结果中。 - 更新哈希表,记录当前前缀和的出现次数。
最终,结果即为满足条件的子数组个数。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> hashMap;
hashMap[0] = 1;//初始化哈希表
int n = nums.size();
int sum = 0;//记录哈希表的键
int count = 0;//记录哈希表的值
for(int i = 0; i < n; i++){
sum += nums[i];
//在哈希表中找到满足条件的子数组
if(hashMap.find(sum - k) != hashMap.end()){
count += hashMap[sum - k];
}
hashMap[sum]++;//更新哈希表的值,即前缀和出现次数,确保每个子数组都被遍历到
}
return count;
}
};
初始化哈希表的作用是,如果遇到数组中单个数满足目标值的情况,确保在值为0时,哈希表中存有对应的值,举个例子,当nums = [1,2,3], k = 1时,满足条件的子数组为nums[0],此处的前缀和为1,即哈希表的键为1,此时执行count+=hashMap[sum-k]时就需要用到hashMap[0]=1
⚠️满足条件的子数组可能是单个元素,也有可能是多个元素组成的子数组,在进行计数时,第一种情况得到的结果是sum - k = 0,第二种情况得到的结果是sum - k = 数组中某个前缀和,此时只需要查询哈希表,便可以得到想要的结果,在计算前缀和时虽然从数组中第一个元素开始遍历,但是实际上是在寻找一个合适的“起点”,使得以当前点 i
为“终点”的子数组和为 k
。这涵盖了所有可能的子数组。