#560 前缀和+哈希 中等

和为 K 的子数组

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给你一个整数数组 nums 和一个整数 k,请你统计并返回该数组中和为 k连续子数组的个数。

示例 1

输入:nums = [1,1,1], k = 2
输出:2

示例 2

输入:nums = [1,2,3], k = 3
输出:2

💭 模拟答题者思考

1. 我先写暴力:枚举 (l,r),算 sum(l..r) 是否为 k,O(n²)。

2. 重复在哪里?每个 r 都在重复找「哪些 l 可行」。

3. 我想把「找 l」变成查表:pre[r] - pre[l-1] = k → pre[l-1] = pre[r] - k。

4. 所以扫到 r 时,只要知道历史上 pre[r]-k 出现几次即可。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
preint定义:扫到当前位置的前缀和
维护:每轮后 pre = sum(nums[0..i])
更新:当扫过 nums[i] 时,pre += nums[i]
cnt[s]map<int,int>定义:历史上前缀和为 s 的出现次数
维护:每轮后 cnt 中 pre 的计数已加 1
更新:统计完 ans 后 cnt[pre]++
ansint定义:和为 k 的连续子数组个数
维护:累计所有满足 pre[r] - pre[l-1] = k 的 (l,r) 对数
更新:每轮 ans += cnt[pre - k]

⌨️ 落码步骤

1. cnt[0] = 1 (空前缀,对应左端点在 index=0 之前)

2. 遍历 nums,更新 pre += nums[i]

3. ans += cnt[pre - k] (查历史中有多少合法左端点)

4. cnt[pre]++ (将当前前缀和记入历史,必须在统计 ans 之后

💻 代码实现

class Solution:
    def subarraySum(self, nums: list[int], k: int) -> int:
        # cnt[s]:历史上前缀和等于 s 的出现次数
        cnt = {0: 1}  # 空前缀 pre=0 已出现 1 次
        pre = 0       # 扫到当前位置的前缀和
        ans = 0       # 和为 k 的连续子数组个数

        for x in nums:
            pre += x
            # pre[r] - pre[l-1] = k  =>  查 pre-k 历史出现几次
            ans += cnt.get(pre - k, 0)
            # 必须把当前 pre 记入历史;若先 cnt[pre]++ 再统计,会多算含当前点的子数组
            cnt[pre] = cnt.get(pre, 0) + 1

        return ans
class Solution {
public:
    int subarraySum(vector& nums, int k) {
        // cnt[s]:历史上前缀和等于 s 的出现次数
        unordered_map cnt;
        // 空前缀 pre=0 已出现 1 次,对应左端点在 index 0 之前的子数组
        cnt[0] = 1;

        int pre = 0;  // 扫到当前位置的前缀和
        int ans = 0;  // 和为 k 的连续子数组个数

        for (int x : nums) {
            pre += x;
            // pre[r] - pre[l-1] = k  =>  pre[l-1] = pre - k
            // 查历史上 pre-k 出现几次,即有多少个合法左端点
            ans += cnt[pre - k];
            // 必须把当前 pre 记入历史;若先 cnt[pre]++ 再统计,会多算含当前点的子数组
            cnt[pre]++;
        }
        return ans;
    }
};
// 时间 O(n),空间 O(n)

📈 复杂度分析

时间复杂度 O(n)
空间复杂度 O(n)

⚠️ 常见坑

顺序错误:必须先 ans += cnt[pre-k],再 cnt[pre]++,否则会把当前点也算进去。

忘记 cnt[0]=1:空前缀的计数至关重要,否则从 index=0 开始的合法子数组会被漏掉。

把「连续子数组」误当「组合求和」:本题是统计连续段的个数,不需要回溯或 DP。

🔍 必测边界 Case

Case 1:k=0 且有零元素
nums = [0,0,0], k = 0 → 输出 6(空前缀 + 各种组合)
Case 2:全部元素都是正数但和为 k
nums = [1,2,3], k = 6 → 输出 1([1,2,3])
Case 3:单元素数组
nums = [5], k = 5 → 输出 1