【数组-5】560.和为K的子数组

题目描述:

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

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

示例 1:

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

示例 2:

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

解题思路:

读完这题我就是想到用双层循环遍历数组,枚举所有子数组,统计和为 k 的个数。

  1. 外层循环 :以每个位置 i 作为子数组起点

  2. 内层循环 :从 i+1 开始往后累加,每累加一次就判断是否等于 k

  3. 统计:如果等于 k,计数器加1

但是这只是暴力解法,并不是最优解,这样的时间复杂度不够好。

源代码如下:

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        int cnt=0;
        for(int i=0;i<n;i++){
            int sum=nums[i];
            if(sum==k)
             cnt++;
            for(int j=i+1;j<n;j++){
                sum+=nums[j];
                if(sum==k)
                  cnt++;
            }
        }
        return cnt;
    }
};

复杂度分析:

  • 时间复杂度:O(n²),双重循环

  • 空间复杂度:O(1),只用了几个变量

最优解法:前缀和 + 哈希表

核心思路:

前缀和prefix[i] 表示 nums[0]nums[i] 的累加和。

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

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

prefix[j-1] == prefix[i] - k

也就是说:对于每个位置 i,我们只需要知道"之前有多少个前缀和等于 prefix[i] - k"。

用哈希表记录每个前缀和出现的次数,就能在 O(1) 时间内查到。

图解:

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

i numsi 当前前缀和 需要找的前缀和 哈希表中该前缀和出现次数 累加cnt 哈希表更新
- - 0 - - - {0:1}(初始)
0 1 1 1-2=-1 0 0 {0:1, 1:1}
1 1 2 2-2=0 1 1 {0:1, 1:1, 2:1}
2 1 3 3-2=1 1 2 {0:1, 1:1, 2:1, 3:1}

结果:2 ✅

为什么初始要放 {0: 1}

因为如果 prefix[i] 本身就等于 k,说明从数组开头到 i 的子数组和为 k,需要 prefix[j-1] = 0 来匹配。初始 {0:1} 就是处理这种情况。

代码实现:

cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> prefixCount;  // 前缀和 → 出现次数
        prefixCount[0] = 1;                   // 初始:前缀和0出现1次
        
        int sum = 0;      // 当前前缀和
        int cnt = 0;      // 满足条件的子数组个数
        
        for (int num : nums) {
            sum += num;   // 更新前缀和
            
            // 查找 sum - k 是否出现过
            if (prefixCount.find(sum - k) != prefixCount.end()) {
                cnt += prefixCount[sum - k];
            }
            
            // 把当前前缀和加入哈希表
            prefixCount[sum]++;
        }
        
        return cnt;
    }
};

复杂度分析:

维度 复杂度
时间复杂度 O(n),一次遍历
空间复杂度 O(n),哈希表最坏存 n 个前缀和

两种方法对比:

对比维度 我的方法(暴力枚举) 前缀和 + 哈希表
时间复杂度 O(n²) O(n)
空间复杂度 O(1) O(n)
核心思想 枚举所有子数组 前缀和之差等于k
代码复杂度 简单 中等

关键点理解:

1.为什么前缀和能优化?

暴力解法的问题在于:对每个起点都要重新累加一遍

前缀和的核心是:

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

所以不需要枚举所有起点,只需要记录每个前缀和出现过几次 ,然后对每个 i 查一下 prefix[i] - k 出现过几次,就相当于找到了所有满足条件的 j

2.哈希表存什么?

键(key) 值(value)
前缀和 这个前缀和出现的次数

3.为什么用 unordered_map 而不是 unordered_set

因为同一个前缀和可能出现多次(比如数组中有负数或0),我们需要统计次数,而不是只判断是否存在。

总结:

这道题是前缀和 + 哈希表的经典入门题,掌握后可以解决很多类似的子数组问题(比如「和可被K整除的子数组」「连续数组」等)。

前缀和 + 哈希表是算法题中非常经典的一种组合技巧,专门解决**"子数组/子区间"相关的问题**。

下面总结这种技巧及其适用的场景:

一、核心适用特征

这类题目通常满足以下三个特征

特征 说明
1. 求子数组/子区间 问题涉及"连续的一段"
2. 求和/计数/条件判断 需要统计满足某条件的子数组个数,或找最长/最短的
3. 条件可转化为前缀和之差 比如 sum[j..i] == kprefix[i] - prefix[j-1] == k

二、典型题型分类

类型1:求和恰好等于 k 的子数组

代表题:LeetCode 560「和为 K 的子数组」

核心公式

复制代码
prefix[i] - prefix[j-1] == k
→ prefix[j-1] == prefix[i] - k

变种

  • 和等于 k 的子数组个数

  • 和等于 k 的最长子数组

  • 和等于 k 的最短子数组


类型2:和可被 K 整除的子数组

代表题:LeetCode 974「和可被 K 整除的子数组」

核心公式

复制代码
(prefix[i] - prefix[j-1]) % K == 0
→ prefix[i] % K == prefix[j-1] % K

关键 :哈希表存余数,而不是前缀和本身。

同类题

  • LeetCode 523「连续的子数组和」(和是 k 的倍数)

  • LeetCode 1590「使数组和能被 P 整除」


类型3:0 和 1 个数相等的子数组

代表题:LeetCode 525「连续数组」

核心思路

  • 把 0 看成 -1,1 看成 +1

  • 问题转化为:和为 0 的最长子数组

同类题

  • LeetCode 1124「表现良好的最长时间段」(把条件转化为 +1/-1)

类型4:前缀和 + 哈希表存"最早出现位置"

代表题:LeetCode 325「和等于 k 的最长子数组长度」(会员题)

核心思路

  • 哈希表存前缀和 → 最早出现的下标

  • 对每个 i,查 prefix[i] - k 最早出现的下标 j

  • 长度 = i - j

同类题

  • LeetCode 525「连续数组」

  • LeetCode 1124「表现良好的最长时间段」


类型5:二维前缀和

代表题:LeetCode 304「二维区域和检索」

核心思路

  • 二维前缀和:prefix[i][j] 表示左上角到 (i,j) 的矩形和

  • 但通常不用哈希表,而是用容斥原理

四、解题模板

模板1:求子数组个数

cpp 复制代码
int subarraySum(vector<int>& nums, int k) {
    unordered_map<int, int> prefixCount;  // 前缀和 → 出现次数
    prefixCount[0] = 1;                   // 初始条件
    int sum = 0, cnt = 0;
    
    for (int num : nums) {
        sum += num;
        if (prefixCount.count(sum - k)) {
            cnt += prefixCount[sum - k];
        }
        prefixCount[sum]++;
    }
    return cnt;
}

模板2:求最长子数组

cpp 复制代码
int findMaxLength(vector<int>& nums) {
    unordered_map<int, int> prefixIndex;  // 前缀和 → 最早出现的下标
    prefixIndex[0] = -1;                  // 初始条件
    int sum = 0, maxLen = 0;
    
    for (int i = 0; i < nums.size(); i++) {
        sum += nums[i];
        if (prefixIndex.count(sum)) {
            maxLen = max(maxLen, i - prefixIndex[sum]);
        } else {
            prefixIndex[sum] = i;  // 只记录最早出现的位置
        }
    }
    return maxLen;
}

模板3:求最短子数组

与最长类似,但哈希表存最晚出现的位置(每次覆盖更新)。

五、关键细节

1. 初始值 {0: 1}{0: -1}

  • 个数prefixCount[0] = 1

  • 长度prefixIndex[0] = -1

原因:处理"从数组开头到当前位置"的子数组。

2. 哈希表存什么?

问题类型 键(key) 值(value)
求和等于k的个数 前缀和 出现次数
求和可被k整除 余数 出现次数
求最长子数组 前缀和 最早下标
求最短子数组 前缀和 最晚下标

3. 什么时候用 unordered_map vs unordered_set

  • 需要统计次数位置unordered_map

  • 只需要判断是否存在unordered_set

六、与其他方法对比

方法 适用场景 时间复杂度
前缀和 + 哈希表 子数组和问题(有负数) O(n)
滑动窗口 子数组和问题(全正数) O(n)
Kadane算法 最大子数组和 O(n)
暴力枚举 小数据量 O(n²)

关键区别

  • 有负数 → 必须用前缀和+哈希表(滑动窗口失效)

  • 全是正数 → 滑动窗口更简单

七、总结口诀

连续子数组,求和或计数;

前缀和做差,哈希表来助;

求个数存次数,求长度存下标;

初始零要加,负数也能查。

相关推荐
影视飓风TIM1 小时前
C++异常与RAII智能指针笔记
开发语言·c++·笔记
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<四十二>:手势识别
c++·人工智能·opencv·计算机视觉·手势识别
草莓熊Lotso1 小时前
【Redis 初阶】C++ 客户端实战:从 RESP 协议到 redis-plus-plus 工程化用法
linux·开发语言·网络·数据库·c++·redis·缓存
郝学胜-神的一滴1 小时前
C++11 工程级应用 10:减少拷贝,让容器跑得更快
服务器·开发语言·c++·游戏引擎·opengl
星子yu1 小时前
【Java数据结构】二叉树 好像就这样?
java·数据结构
Beyond_System|系统之外2 小时前
【学编程】Python基础编程题100道(1-20)
java·数据结构·算法
维克兜率天2 小时前
【维克】一个极端值,毁掉了整个因子
大数据·人工智能·python·算法·机器学习
船厂电气自动化ai大模型2 小时前
AI大模型与数学 第75课 基、维度、向量空间坐标
数据结构·线性代数·算法·机器学习·推荐算法
博界IT精灵2 小时前
专题1:线性表
考研·算法