【Hot100|10-LeetCode 560. 和为 K 的子数组】

这段代码是解决 LeetCode 560. 和为 K 的子数组 问题的经典前缀和 + 哈希表优化解法,核心目标是高效统计数组中和为 k 的连续子数组的个数,时间复杂度优化到 O (n),空间复杂度 O (n)。下面从问题理解→核心思路→代码逐行解析→实例演示四个维度详细讲解:

一、问题理解

问题要求

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

  • 子数组:连续的元素序列(必须相邻,如 nums[1..3] 是子数组,nums[1] 和 nums[3] 不相邻则不是)。
  • 示例:输入 nums = [1,1,1]k = 2,输出 2(子数组 [0,1] 和 [1,2] 的和均为 2);输入 nums = [1,2,3]k = 3,输出 2(子数组 [0,0] 之和 1+2=3?不,实际是 [0,1] 之和 3,[2,2] 之和 3,共 2 个)。

二、核心思路:前缀和 + 哈希表计数

暴力解法(枚举所有子数组并计算和)时间复杂度为 O (n²),效率极低。该解法通过前缀和转化问题,结合哈希表记录前缀和出现次数,实现线性时间求解,核心思路:

  1. 前缀和定义:设 preSum[i] 表示数组前 i 个元素的和(即 preSum[i] = nums[0] + nums[1] + ... + nums[i-1])。例如:nums = [a,b,c],则 preSum[0] = 0preSum[1] = apreSum[2] = a+bpreSum[3] = a+b+c

  2. 子数组和与前缀和的关系:子数组 nums[j..i-1](从索引 j 到 i-1)的和 = preSum[i] - preSum[j]。题目要求子数组和为 k,即 preSum[i] - preSum[j] = k → 等价于 preSum[j] = preSum[i] - k

  3. 哈希表计数:用哈希表 preSumCount 记录每个前缀和 preSum 出现的次数。对于当前前缀和 preSum[i],若 preSum[i] - k 存在于哈希表中,则说明有 preSumCount[preSum[i]-k] 个 j 满足条件,即有这么多个子数组和为 k,累加至结果 ans

  4. 初始化处理:初始时 preSumCount 存入 (0, 1),原因是:当 preSum[i] = k 时,preSum[i] - k = 0,此时需要统计到 preSum[0] = 0 这一情况(对应子数组从索引 0 开始到 i-1)。

三、代码逐行解析

java

运行

import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;

class Solution {
    public int subarraySum(int[] nums, int k) {
        // 1. 记录和为k的子数组总数(结果)
        int ans = 0;
        // 2. 记录当前前缀和(preSum[i] = nums[0] + ... + nums[i-1])
        int preSum = 0;
        // 3. 哈希表:key=前缀和,value=该前缀和出现的次数
        Map<Integer, Integer> preSumCount = new HashMap<>();
        // 4. 初始化:前缀和为0的情况出现1次(对应子数组从0开始的场景)
        preSumCount.put(0, 1);
        
        // 5. 遍历数组,更新前缀和并统计结果
        for (int num : nums) {
            // 5.1 更新当前前缀和(加上当前元素)
            preSum += num;
            
            // 5.2 核心:若preSum - k存在于哈希表中,说明有对应子数组和为k
            if (preSumCount.containsKey(preSum - k)) {
                ans += preSumCount.get(preSum - k);
            }
            
            // 5.3 将当前前缀和加入哈希表(次数+1,若已存在则更新)
            preSumCount.put(preSum, preSumCount.getOrDefault(preSum, 0) + 1);
        }
        
        // 6. 返回结果
        return ans;
    }
}

四、实例演示(直观理解过程)

以测试用例 nums = [1,1,1]k = 2 为例,演示前缀和、哈希表和结果的变化:

步骤遍历元素 num当前 preSum(累加后)preSum - kpreSumCount 中是否存在 preSum - k本次累加 ans(加上对应次数)preSumCount 更新(key=preSum,value = 次数)ans 累计
初始-0---{(0,1)}0
110 + 1 = 11 - 2 = -1否(哈希表中无 - 1)+0加入 (1,1) → {(0,1), (1,1)}0
211 + 1 = 22 - 2 = 0是(0 的次数为 1)+1加入 (2,1) → {(0,1), (1,1), (2,1)}1
312 + 1 = 33 - 2 = 1是(1 的次数为 1)+1加入 (3,1) → {(0,1), (1,1), (2,1), (3,1)}2

最终结果ans=2,与预期一致(对应子数组 [0,1] 和 [1,2])。

另一个测试用例 nums = [1,2,3]k=3

步骤numpreSumpreSum -k是否存在累加 anspreSumCount 更新ans 累计
初始-0---{(0,1)}0
1111-3=-2+0{(0,1), (1,1)}0
2233-3=0是(次数 1)+1{(0,1), (1,1), (3,1)}1
3366-3=3是(次数 1)+1{(0,1), (1,1), (3,1), (6,1)}2

最终结果ans=2(对应子数组 [0,1](1+2=3)和 [2,2](3=3))。

五、关键细节解析

1. 为什么初始化preSumCount.put(0, 1)

这是为了处理子数组从索引 0 开始的情况。例如:当 preSum = k 时(如上述第二个测试用例中 preSum=3k=3),preSum - k = 0,此时哈希表中 0 的次数为 1,会将这一情况统计到 ans 中,即子数组 [0..i-1] 是有效的。若不初始化,这种情况会被遗漏。

2. 哈希表的作用:避免重复计算前缀和次数

哈希表记录了每个前缀和出现的次数,例如:若 preSum = 5 出现过 3 次,当后续 preSum[i] - k = 5 时,说明有 3 个不同的 j 满足条件,即有 3 个和为 k 的子数组,直接累加 3 即可,无需重新计算。

3. 为什么能统计所有连续子数组?

因为遍历过程中,preSum 是 “累加当前元素” 的过程,覆盖了所有可能的前缀和 preSum[i];而 preSum[i] - k 的查询,覆盖了所有可能的 preSum[j],因此所有满足 preSum[i] - preSum[j] = k 的子数组 [j..i-1] 都会被统计。

六、复杂度分析

  • 时间复杂度:O(n)。仅遍历数组一次(O (n)),哈希表的 put 和 get 操作均为 O (1)(平均情况),因此总时间复杂度为线性级别。

  • 空间复杂度:O(n)。哈希表 preSumCount 最多存储 n+1 个不同的前缀和(数组长度为 n 时,前缀和最多有 n+1 个:preSum[0] 到 preSum[n]),因此空间复杂度为 O (n)。

七、总结

该解法的核心是 **“前缀和转化问题 + 哈希表记录频率”:通过前缀和将 “子数组和为 k” 转化为 “两个前缀和的差值为 k”,再用哈希表高效统计符合条件的前缀和出现次数,最终在 O (n) 时间内解决问题。这种 “前缀和 + 哈希表” 的思路是解决子数组和问题 ** 的经典范式,可迁移到 “和为 0 的子数组”“子数组和的范围” 等类似问题中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值