力扣刷题日记10---剑指 Offer II 010. 和为 k 的子数组
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
示例 2 :
输入:nums = [1,2,3], k = 3
输出: 2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
思路1:前缀和 + 双层循环
这道题可以借鉴第八题的思路,用前缀和,然后使用双层 for 循环 遍历所有子数组。
顺便提一下为啥不能用滑动窗口,因为滑动窗口是要根据 窗口内的元素和 满足或者不满足某个条件来调整窗口大小的。但是在本题中,数组里存在负数,如果窗口内的元素和不满足某个条件,我们无从判断该如何调整大小,因此滑动窗口法不适用本题。
代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int size = nums.size();
vector<int> vpre(size + 1, 0);
for(int i = 0; i < size; ++i)
vpre[i + 1] = vpre[i] + nums[i];
int ret = 0;
for(int i = 0; i <= size; ++i){
for(int j = i + 1; j <= size; ++j){
if(vpre[j] - vpre[i] == k)
ret++;
}
}
return ret;
}
};
时间复杂度:O(n2),会超时;空间复杂度:O(n)。
思路2:前缀和 + map容器
由于双层 for 循环时间复杂度较高,因此可以考虑用 空间换时间 的方法,降低时间复杂度。
这里使用的是 STL 中的 map 容器。map<int, int> 中,键存储的是前缀和,值存储的是该前缀和出现的次数。
举例:假设数组前 j 个元素之和为 total。现存在一个 i,使前 i 个元素之和为 total - k,那么数组中 i 到 j 之间元素的和就是我们要找的 k,即找到了满足条件的子数组。
代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int size = nums.size();
vector<int> vpre(size + 1, 0);
for(int i = 0; i < size; ++i)
vpre[i + 1] = vpre[i] + nums[i];
int ret = 0;
map<int, int> m;
for(int i = 0; i <= size; ++i){
if(m.count(vpre[i] - k)) //count用来判定关键字是否出现,如果出现返回 1,否则返回 0
ret += m[vpre[i] - k];
m[vpre[i]] += 1;
}
return ret;
}
};
时间复杂度:O(n);空间复杂度:O(n)。
思路3:前缀和 + 哈希表
第二种思路还可以进一步优化:首先,前缀和可以不用一个 vector 数组来存放,可以在遍历的时候直接放入键值对中;其次,使用哈希表缩短 map 的查询时间。
需要说明的是,要先查询哈希表,然后把前缀和以及出现次数存入哈希表。原因是:如果先存,那么会存在当 k = 0 时会把刚存进去的纪录也算上的问题,但是此时的空数组是不符合要求的。因此先查询,后存储。
代码如下:
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int ret = 0;
int total = 0;
unordered_map<int, int> um;
um[0] = 1; //空数组出现一次
for(int& n : nums){
total += n;
if(um.count(total - k))
ret += um[total - k];
um[total]++;
}
return ret;
}
};
时间复杂度:O(n);空间复杂度:O(n)。