主题
前置题目
本题是连续子数组和问题,可以用前缀和处理。
请先完成前缀和模板题 303. 区域和检索 - 数组不可变,并阅读 我的题解。
转化
回顾 303 题的前缀和的定义:
注意
是一个长为 的数组,第一个数是 。
设
问题转化为:
中有多少对下标 满足 且 ?
写成
枚举右,维护左
以

如果用二重循环暴力枚举有多少个
从两数之和中,我们可以学到什么?我们可以把
枚举当前的前缀和
比如
用这个视角,再来算算上图中的那
| 解释 | ||||
|---|---|---|---|---|
| 无 | ||||
一共有
具体来说,在遍历
答疑
问:为什么这样做可以不重不漏地计算?
答:暴力做法是,外层循环枚举
问:为什么要把
答:举个最简单的例子,
问:为什么代码中要先更新
答:不行,这会在
问:为什么这题不适合用滑动窗口做?
答:滑动窗口需要满足单调性,当右端点元素进入窗口时,窗口元素和是不能减少的。本题
写法一:两次遍历
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
s = [0] * (len(nums) + 1)
for i, x in enumerate(nums):
s[i + 1] = s[i] + x
cnt = defaultdict(int)
ans = 0
for sj in s:
ans += cnt[sj - k]
cnt[sj] += 1
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
vector<int> s(n + 1);
for (int i = 0; i < n; i++) {
s[i + 1] = s[i] + nums[i];
}
unordered_map<int, int> cnt;
int ans = 0;
for (int sj : s) {
// 注意不要直接 += cnt[sj-k],如果 sj-k 不存在,会插入 sj-k
ans += cnt.contains(sj - k) ? cnt[sj - k] : 0;
cnt[sj]++;
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 为 的长度。 - 空间复杂度:
。
写法二:一次遍历 · 其一
我们可以一边计算前缀和,一边遍历前缀和。
在遍历
对比一下,两次遍历的代码循环了
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt = defaultdict(int)
cnt[0] = 1 # s[0]=0 单独统计
ans = s = 0
for x in nums:
s += x
ans += cnt[s - k]
cnt[s] += 1
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> cnt = {{0, 1}}; // s[0]=0 单独统计
int ans = 0, s = 0;
for (int x : nums) {
s += x;
// 注意不要直接 += cnt[s-k],如果 s-k 不存在,这会插入 s-k,消耗更多空间
ans += cnt.contains(s - k) ? cnt[s - k] : 0;
cnt[s]++;
}
return ans;
}
};写法三:一次遍历 · 其二
在同一轮循环中,先把
这样写无需初始化
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt = defaultdict(int)
ans = s = 0
for x in nums:
cnt[s] += 1
s += x
ans += cnt[s - k]
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> cnt;
int ans = 0, s = 0;
for (int x : nums) {
cnt[s]++;
s += x;
// 注意不要直接 += cnt[s-k],如果 s-k 不存在,这会插入 s-k,消耗更多空间
ans += cnt.contains(s - k) ? cnt[s - k] : 0;
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 为 的长度。 - 空间复杂度:
,其中 为不同前缀和的个数。如果设置了哈希表的容量,则空间复杂度为 。
变形题
- 改成计算元素和等于
的最短子数组,要怎么做? - 改成计算元素和等于
的最长子数组,要怎么做? - 改成计算元素和等于
的所有子数组的长度之和,要怎么做? - 改成元素和至多为
,要怎么做?见 363. 矩形区域不超过 K 的最大数值和。 - 改成计算元素和为奇数的子数组个数,要怎么做?
欢迎在评论区分享你的思路/代码。
提示:思考题 4 可以枚举上下边界,转成一维数组。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府