【力扣100题】61.和为 K 的子数组

题目描述

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

子数组是数组中元素的连续非空序列。

示例

复制代码
示例 1:
输入:nums = [1,1,1], k = 2
输出:2

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

提示:

  • 1 <= nums.length <= 2 * 10^4
  • -1000 <= nums[i] <= 1000
  • -10^7 <= k <= 10^7

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 备注
前缀和 + 哈希表 用哈希表记录前缀和出现次数 O(n) O(n) 推荐解法
暴力枚举 枚举所有子数组 O(n^2) O(1) 会超时
前缀和数组 先计算所有前缀和再统计 O(n^2) O(n) 不优化

一、核心解法:前缀和 + 哈希表

核心思想

对于子数组问题,核心技巧是前缀和

复制代码
设 prefix[i] = nums[0] + nums[1] + ... + nums[i]

子数组 nums[i...j] 的和 = prefix[j] - prefix[i-1]

我们要找:prefix[j] - prefix[i-1] = k
即:prefix[i-1] = prefix[j] - k

所以对于每个位置 j,我们只需要知道有多少个前缀和等于 prefix[j] - k。

关键洞察

复制代码
以 nums = [1,1,1], k = 2 为例:

前缀和序列:
  prefix[-1] = 0  (空数组的前缀和)
  prefix[0]  = 1  (nums[0])
  prefix[1]  = 2  (nums[0] + nums[1])
  prefix[2]  = 3  (nums[0] + nums[1] + nums[2])

枚举每个位置作为子数组结尾:
  j=0: prefix[0]=1, 找 prefix[i-1]=1-2=-1,有 0 个
  j=1: prefix[1]=2, 找 prefix[i-1]=2-2=0, 有 1 个  (nums[0..1])
  j=2: prefix[2]=3, 找 prefix[i-1]=3-2=1, 有 1 个  (nums[1..2])

总计:2 个

图解

复制代码
nums = [1, 1, 1], k = 2

前缀和计算过程:
  空数组: prefix = 0
  加 1:   prefix = 1
  加 1:   prefix = 2
  加 1:   prefix = 3

哈希表演化过程:

初始状态:
  hash = {0: 1}  // prefix[-1] = 0,出现 1 次

处理 nums[0] = 1:
  prefix = 1
  prefix - k = -1
  hash 中没有 -1,ans += 0
  hash[1]++

  hash = {0: 1, 1: 1}

处理 nums[1] = 1:
  prefix = 2
  prefix - k = 0
  hash 中有 0,出现 1 次,ans += 1  // 子数组 [0..1]
  hash[2]++

  hash = {0: 1, 1: 1, 2: 1}

处理 nums[2] = 1:
  prefix = 3
  prefix - k = 1
  hash 中有 1,出现 1 次,ans += 1  // 子数组 [1..2]
  hash[3]++

  hash = {0: 1, 1: 1, 2: 1, 3: 1}

最终: ans = 2

二、算法流程图

复制代码
输入: nums = [1, 1, 1], k = 2

初始化:
  hash = {0: 1}   // prefix[-1] = 0
  prefix = 0
  ans = 0

i=0, num=1:
  prefix = 0 + 1 = 1
  prefix - k = -1
  hash[-1]? 不存在,ans += 0
  hash[1] = 1

i=1, num=1:
  prefix = 1 + 1 = 2
  prefix - k = 0
  hash[0] = 1,ans += 1  // 找到 1 个子数组
  hash[2] = 1

i=2, num=1:
  prefix = 2 + 1 = 3
  prefix - k = 1
  hash[1] = 1,ans += 1  // 找到 1 个子数组
  hash[3] = 1

输出: ans = 2

三、完整代码实现

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        int prefix = 0;   // 当前前缀和
        int ans = 0;      // 答案计数

        // 初始化:空数组的前缀和为 0,出现 1 次
        hash[0] = 1;

        for (int num : nums) {
            prefix += num;                      // 更新前缀和

            // 找有多少个前缀和等于 prefix - k
            // 即有多少个子数组和为 k
            if (hash.find(prefix - k) != hash.end()) {
                ans += hash[prefix - k];
            }

            // 记录当前前缀和出现的次数
            hash[prefix]++;
        }

        return ans;
    }
};

四、逐行解析

cpp 复制代码
unordered_map<int, int> hash;
  • 哈希表:key = 前缀和,value = 该前缀和出现的次数
cpp 复制代码
int prefix = 0;
int ans = 0;
  • prefix:当前遍历位置的前缀和
  • ans:累计满足条件的子数组个数
cpp 复制代码
hash[0] = 1;
  • 初始化:空数组的前缀和为 0,初始出现 1 次
  • 这个初始化很重要,因为子数组可能从索引 0 开始
cpp 复制代码
for (int num : nums) {
    prefix += num;
  • 遍历数组,累加当前前缀和
cpp 复制代码
if (hash.find(prefix - k) != hash.end()) {
    ans += hash[prefix - k];
}
  • 如果存在前缀和等于 prefix - k
  • 说明存在子数组和为 k
  • 加上该前缀和出现的次数
cpp 复制代码
hash[prefix]++;
  • 记录当前前缀和出现的次数

五、为什么初始化 hash[0] = 1?

复制代码
当子数组从索引 0 开始时:

nums = [3, 1, 2], k = 6

处理 nums[0] = 3:
  prefix = 3
  prefix - k = -3
  hash[-3] 不存在

如果没有初始化 hash[0] = 1,会漏掉 [3, 1, 2] 这个子数组!

因为 [3, 1, 2] 的和 = prefix[2] - prefix[-1]
而 prefix[-1] = 0,hash[0] = 1

初始化 hash[0] = 1 就是为了处理从 0 开始的子数组。

六、与第 53 题(最大子数组和)对比

维度 第 53 题 最大子数组和 第 560 题 和为 K 的子数组
问题类型 求最大和 统计个数
方法 贪心/DP 前缀和 + 哈希表
转移 dp[i] = max(dp[i-1] + nums[i], nums[i]) 统计 prefix - k 出现的次数
时间复杂度 O(n) O(n)
空间复杂度 O(1) O(n)

七、复杂度分析

方法 时间复杂度 空间复杂度 备注
前缀和 + 哈希表 O(n) O(n) 推荐
暴力枚举 O(n^2) O(1) 会超时
前缀和数组 O(n^2) O(n) 不优化

详细分析:

复制代码
时间复杂度:
  遍历一次数组:O(n)
  哈希表查找/插入:平均 O(1)
  总计:O(n)

空间复杂度:
  哈希表最坏情况 O(n) 个不同的前缀和
  输出数组不算额外空间
  总计:O(n)

八、边界情况分析

情况 处理方式
全是正数 正常处理
全是负数 正常处理
有 0 正常处理,0 不影响前缀和
k = 0 正常处理,找两段子数组和相等
空数组 不进入循环,返回 0

示例:k = 0

复制代码
nums = [0, 0, 0], k = 0

处理过程:
  i=0: prefix=0, prefix-k=0, hash[0]=1, ans+=1  // [0]
  i=1: prefix=0, prefix-k=0, hash[0]=2, ans+=2  // [0], [0,0]
  i=2: prefix=0, prefix-k=0, hash[0]=3, ans+=3  // [0], [0,0], [0,0,0]

结果:ans = 6

验证:[0] 在三个位置各出现一次,[0,0] 出现两次,[0,0,0] 出现一次

九、面试追问 FAQ

问题 回答要点
Q: 为什么前缀和能解决这个问题? 子数组和 = 前缀和差,我们用哈希表记录前缀和出现次数,快速查找差为 k 的情况
Q: 哈希表的作用是什么? 将查找前缀和出现次数从 O(n) 降到 O(1),实现 O(n) 时间复杂度
Q: 为什么初始化 hash[0] = 1? 处理从索引 0 开始的子数组,此时 prefix - k = 0 - k = -k
Q: 时间复杂度为什么是 O(n)? 遍历一次,哈希表操作均摊 O(1)
Q: 空间复杂度为什么是 O(n)? 哈希表最坏情况存储 n 个不同的前缀和
Q: 能否用数组代替哈希表? 不能,因为前缀和范围可能很大(负数到正数),哈希表更灵活

十、相关题目

题目编号 题目名称 难度 核心差异
560 和为 K 的子数组 中等 基础题,统计个数
53 最大子数组和 中等 求最大和,贪心/DP
1248 统计「优美子数组」 中等 统计奇数个数为 K
974 和可被 K 整除的子数组 中等 前缀和模 K
剑指 Offer 42 连续子数组的最大和 简单 求最大和
724 寻找数组的中心下标 简单 前缀和基础

十一、总结

要点 内容
核心思想 前缀和 + 哈希表
关键公式 子数组和 = prefix[j] - prefix[i-1]
哈希作用 快速查找 prefix - k 出现的次数
初始化 hash[0] = 1,处理从 0 开始的子数组
时间复杂度 O(n)
空间复杂度 O(n)
关键洞察 用空间换时间,将 O(n^2) 优化到 O(n)

和为 K 的子数组是前缀和思想的经典应用,通过哈希表快速查找前缀和差为 k 的情况,将暴力枚举的 O(n^2) 优化到 O(n)。


相关推荐
兰令水2 小时前
leecodecode【滑动窗口】【2026.5.27打卡-java版本】
java·数据结构·算法
Brilliantwxx2 小时前
【算法题】 面试级别的二叉树题目OJ复习(上)
数据结构·c++·笔记·算法·面试
sheeta19982 小时前
LeetCode 补拙笔记 日期:2026.05.27 题目:61. 旋转链表
笔记·leetcode·链表
Run_Teenage2 小时前
算法:图的存储与遍历,最小生成树(Prim算法,kruskal算法)
算法·深度优先·图论
WWTYYDS_6662 小时前
手写 C++ Any 类:深入理解多态与模板
开发语言·c++·算法
玉树临风ives2 小时前
atcoder ABC 459 题解
算法
EllinY11 小时前
CF2217E Definitely Larger 题解
c++·笔记·算法·构造
玖釉-14 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法