LEETCODE 560Medium
和为 K 的子数组
子数组和为 k 等价于两个前缀和相差 k,用哈希表数“sum - k 出现过几次”,一遍扫完。
问题拆解
统计数组里有多少个连续子数组的和恰好等于 k。枚举所有子数组是 O(n^2) 个,逐个求和更慢;用前缀和能把“求任意子数组的和”降到 O(1),但枚举左右端点仍是 O(n^2)。另外要注意数组里有负数,滑动窗口的单调性不成立,这条路走不通。
真正的突破口是把条件改写成前缀和的语言:记 prefix[i] 为前 i 个数的和,子数组 (j, i] 的和为 k,等价于 prefix[i] - prefix[j] = k,即 prefix[j] = prefix[i] - k。于是问题变成:扫到位置 i 时,前面有多少个前缀和等于 sum - k?这是个典型的“边扫边数”问题,拿哈希表记录每个前缀和出现的次数即可。
“子数组和等于 k”翻译成“两个前缀和相差 k”,配对计数就交给哈希表——这和两数之和是同一个动作:找互补值,边扫边存。
前缀和加哈希计数
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
count.put(0, 1); // 空前缀,让从下标 0 开始的子数组也能配上对
int sum = 0, ans = 0;
for (int x : nums) {
sum += x;
ans += count.getOrDefault(sum - k, 0); // 前面每个 sum-k 都对应一个子数组
count.merge(sum, 1, Integer::sum);
}
return ans;
}
def subarraySum(nums: List[int], k: int) -> int:
count = defaultdict(int)
count[0] = 1 # 空前缀,让从下标 0 开始的子数组也能配上对
total = ans = 0
for x in nums:
total += x
ans += count[total - k] # 前面每个 total-k 都对应一个子数组
count[total] += 1
return ans
func subarraySum(nums []int, k int) int {
count := map[int]int{0: 1} // 空前缀,让从下标 0 开始的子数组也能配上对
sum, ans := 0, 0
for _, x := range nums {
sum += x
ans += count[sum-k] // 前面每个 sum-k 都对应一个子数组
count[sum]++
}
return ans
}
pub fn subarray_sum(nums: Vec<i32>, k: i32) -> i32 {
let mut count = std::collections::HashMap::new();
count.insert(0, 1); // 空前缀,让从下标 0 开始的子数组也能配上对
let (mut sum, mut ans) = (0, 0);
for x in nums {
sum += x;
ans += count.get(&(sum - k)).copied().unwrap_or(0); // 前面每个 sum-k 都对应一个子数组
count.entry(sum).and_modify(|c| *c += 1).or_insert(1);
}
ans
}
两处细节最容易翻车。第一是初始化 {0: 1}:它代表“空前缀”的和 0 出现过一次,没有它,所有从下标 0 开始、恰好和为 k 的子数组都会被漏数——比如 nums = [3], k = 3 会错误地返回 0。第二是先查再存的顺序:必须先用 sum - k 去查表、累加答案,再把当前 sum 存进去;反过来的话,当 k = 0 时当前前缀和会和自己配对,凭空多出一段长度为零的“子数组”。
复杂度
| 指标 | 复杂度 | 原因 |
|---|---|---|
| 时间 | O(n) |
一次遍历,哈希表读写均摊 O(1) |
| 空间 | O(n) |
哈希表最多存 n+1 个不同的前缀和 |
可以迁移的模式
- 子数组的和、异或等区间量,都能改写成两个前缀量的关系,把区间问题变成配对问题;
- “边扫边查互补值、再把自己入表”的顺序保证只和左侧配对,天然不重不漏;
- 哨兵条目(这里的
{0: 1})对应“空前缀”,凡是配对可能顶到数组开头的场景都要想到它。
同一副骨架换个谓词就是新题:和被 k 整除(974)、连续数组(525)、路径总和 III(437),识别出“前缀 + 哈希配对”后它们全是一道题。