LeetCode 930中等前缀和/滑窗
和相同的二元子数组 图解题解
这道题到底在问什么
给定二元数组 nums(只含 0/1)与目标 goal,统计和恰好等于 goal 的非空连续子数组个数。
- 输入
- nums=[1,0,1,0,1], goal=2
- 输出
- 4
- 输入
- nums=[0,0,0,0,0], goal=0
- 输出
- 15 (所有非空子数组)
最优解:一步一步想明白
- 3记住「子数组和=goal ⟺ 找更早的前缀和=当前前缀和−goal」,下面每帧都在套它。
- 4开局:前缀和为 0、答案为 0。表里先放一个「前缀和 0 出现 1 次」——它代表从头开始的子数组(空前缀),别漏。
- 5读到第 0 个(1),前缀和变成 1。要找前缀和 = 1−2 = -1:表里没有,这一步贡献 0。
- 6结算:答案加 0,变成 0。再把当前前缀和 1 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 7读到第 1 个(0),前缀和变成 1。要找前缀和 = 1−2 = -1:表里没有,这一步贡献 0。
- 8结算:答案加 0,变成 0。再把当前前缀和 1 记进表(高亮行,现 2 次),供后面的元素来匹配。
- 9读到第 2 个(1),前缀和变成 2。要找前缀和 = 2−2 = 0:表里有 1 个(高亮行),说明有 1 个以这里结尾、和为 2 的子数组。
- 10结算:答案加 1,变成 1。再把当前前缀和 2 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 11读到第 3 个(0),前缀和变成 2。要找前缀和 = 2−2 = 0:表里有 1 个(高亮行),说明有 1 个以这里结尾、和为 2 的子数组。
- 12结算:答案加 1,变成 2。再把当前前缀和 2 记进表(高亮行,现 2 次),供后面的元素来匹配。
- 13读到第 4 个(1),前缀和变成 3。要找前缀和 = 3−2 = 1:表里有 2 个(高亮行),说明有 2 个以这里结尾、和为 2 的子数组。
- 14结算:答案加 2,变成 4。再把当前前缀和 3 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 15读到第 5 个(1),前缀和变成 4。要找前缀和 = 4−2 = 2:表里有 2 个(高亮行),说明有 2 个以这里结尾、和为 2 的子数组。
- 16结算:答案加 2,变成 6。再把当前前缀和 4 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 17读到第 6 个(0),前缀和变成 4。要找前缀和 = 4−2 = 2:表里有 2 个(高亮行),说明有 2 个以这里结尾、和为 2 的子数组。
- 18结算:答案加 2,变成 8。再把当前前缀和 4 记进表(高亮行,现 2 次),供后面的元素来匹配。
- 19读到第 7 个(1),前缀和变成 5。要找前缀和 = 5−2 = 3:表里有 1 个(高亮行),说明有 1 个以这里结尾、和为 2 的子数组。
- 20结算:答案加 1,变成 9。再把当前前缀和 5 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 21读到第 8 个(0),前缀和变成 5。要找前缀和 = 5−2 = 3:表里有 1 个(高亮行),说明有 1 个以这里结尾、和为 2 的子数组。
- 22结算:答案加 1,变成 10。再把当前前缀和 5 记进表(高亮行,现 2 次),供后面的元素来匹配。
- 23读到第 9 个(1),前缀和变成 6。要找前缀和 = 6−2 = 4:表里有 2 个(高亮行),说明有 2 个以这里结尾、和为 2 的子数组。
- 24结算:答案加 2,变成 12。再把当前前缀和 6 记进表(高亮行,现 1 次),供后面的元素来匹配。
- 25全程扫完,一共数到 12 个和为 2 的连续子数组。靠的是「每到一个位置,就用哈希表 O(1) 查有多少个匹配的旧前缀和」,整体一遍 O(n)。
⚠️ 容易写错的地方
✗ 错:忘了初始化 count{0:1}
✓ 对:必须先放「前缀和 0 出现 1 次」
否则会漏掉「从下标 0 开始、和正好为 goal」的子数组
✗ 错:先 count[prefix]++ 再查
✓ 对:必须先查 count[prefix−goal] 再把自己入表
顺序反了会把当前前缀和算进自己的匹配(goal=0 时尤其错)
✗ 错:只想到双重循环枚举
✓ 对:前缀和+哈希一遍即可
枚举所有子数组是 O(n²),大数据会超时
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import defaultdict
class Solution:
def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
count = defaultdict(int)
count[0] = 1
prefix = ans = 0
for x in nums:
prefix += x
ans += count[prefix - goal]
count[prefix] += 1
return ansC++
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
int numSubarraysWithSum(vector<int>& nums, int goal) {
unordered_map<int,int> count;
count[0] = 1;
int prefix = 0, ans = 0;
for (int x : nums) {
prefix += x;
if (count.count(prefix - goal)) ans += count[prefix - goal];
count[prefix]++;
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int numSubarraysWithSum(int[] nums, int goal) {
Map<Integer,Integer> count = new HashMap<>();
count.put(0, 1);
int prefix = 0, ans = 0;
for (int x : nums) {
prefix += x;
ans += count.getOrDefault(prefix - goal, 0);
count.put(prefix, count.getOrDefault(prefix, 0) + 1);
}
return ans;
}
}复杂度
时间
O(n)
一遍遍历,每步哈希查/改 O(1)
空间
O(n)
哈希表最多存 n+1 种前缀和
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 和相同的二元子数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题还有别的 O(n) 解法吗?+
有,滑动窗口:因为是 0/1 非负数组,定义 atMost(S)=和不超过 S 的子数组个数(双指针),答案 = atMost(goal) − atMost(goal−1)。前缀和+哈希更通用(含负数也行),滑窗常数更小但依赖非负性。
如果数组含负数,哪种解法还成立?+
前缀和+哈希仍成立(它不依赖单调性);滑动窗口的 atMost 解法会失效,因为有负数时窗口和不再随右移单调增。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 和相同的二元子数组 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。