LeetCode热题100——560.和为K的子数组

59 阅读2分钟

题目概述

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

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

 

示例:

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

 

提示:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107

解法

前缀和+哈希表

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var subarraySum = function(nums, k) {
    const mp = new Map();
    mp.set(0, 1);
    let count = 0, pre = 0;
    for (const x of nums) {
        pre += x;
        if (mp.has(pre - k)) {
            count += mp.get(pre - k);
        }
        if (mp.has(pre)) {
            mp.set(pre, mp.get(pre) + 1);
        } else {
            mp.set(pre, 1);
        }
    }
    return count;
};

题目旨在寻找数组nums中的子数组S[j,i]满足S[j,i] = k

定义前缀和S[i]为数组中的前i个元素之和 那么S[j,i] = S[i] - S[j-1] 那么问题转化为寻找S[j-1] = S[i] - k

当我们遍历到当前索引 ii 并计算出前缀和 S[i]S[i] 时,我们只需要知道之前有多少个索引 j1j-1 处的前缀和恰好等于 S[i]kS[i] - k,这些索引的数量就是以 ii 结尾、且和为 kk 的子数组的个数。

  • mp (哈希表 Map): 用于存储 前缀和 及其出现的 次数

    • key: 某个前缀和的值(即 S[j1]S[j-1])。
    • value: 该前缀和出现的次数。
  • pre: 当前遍历到的前缀和 S[i]S[i]

  • count: 最终结果,和为 kk 的子数组的总个数。

初始化与边界条件

  • mp.set(0, 1) : 处理以数组第一个元素开始的子数组。

    • 如果子数组从索引 00 开始,即 nums[0..i],那么它的和是 S[i]S[i]
    • 公式为 Sum(0, i) = S[i] - S[-1]。定义空前缀和 S[1]=0S[-1] = 0
    • 通过初始化 mp.set(0, 1),相当于提前记录了空前缀和 S[1]=0S[-1]=0 出现了一次。这样,当 S[i]=kS[i] = k 时,mp.has(k - k) = mp.has(0) 就会找到这个 S[1]S[-1],并正确地将计数加一。

算法通过循环遍历数组中的每一个元素 x,并在每一步执行两个主要动作:

查找目标前缀和并计数

  • if (mp.has(pre - k)) { count += mp.get(pre - k); }
  • 在计算出当前前缀和 S[i] 后,立即去哈希表中查找目标前缀和 S[j1]=S[i]kS[j-1] = S[i] - k
  • 如果找到,说明之前有 MM 个子数组的前缀和为 S[i]kS[i] - k。那么,当前以 ii 结尾、和为 kk 的子数组就有 MM 个,将 MM 加到 count 中。

更新当前前缀和的出现次数

if (mp.has(pre)) {
    mp.set(pre, mp.get(pre) + 1);
} else {
    mp.set(pre, 1);
}
  • 将当前计算得到的 前缀和 (S[i]S[i]) 及其出现次数记录或更新到哈希表中,供后续遍历时使用。

时间,空间复杂度

时间复杂度:O(N)O(N)

  • 算法只进行了一次数组遍历。
  • 哈希表(Map)的查找、插入和更新操作的平均时间复杂度均为 O(1)O(1)
  • 因此,总时间复杂度为线性的 O(N)O(N)

空间复杂度:O(N)O(N)

最坏情况下,数组中所有前缀和都不同(例如递增数组),哈希表需要存储 NN 个不同的前缀和以及初始的 S[1]=0S[-1]=0。因此,空间复杂度为 O(N)O(N)