LeetCode 热题 100 之第10题 和为 K 的子数组(JavaScript篇)

127 阅读5分钟

传送门:560. 和为 K 的子数组 - 力扣(LeetCode)

🧩 题目

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

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

✅ 示例

示例 1:

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

示例 2:

输入: nums = [1,2,3], k = 3
输出: 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) {
    let count = 0 ,currentSum = 0;          
    const prefixSumCount = new Map();  
    prefixSumCount.set(0, 1);     
    for (const num of nums) {
        currentSum += num;       
        const requiredPrefixSum = currentSum - k;
        count += prefixSumCount.get(requiredPrefixSum) ?? 0;
        prefixSumCount.set(
            currentSum,
            (prefixSumCount.get(currentSum) ?? 0) + 1
        );
    }
    return count;
};

⏱️ 时间复杂度:O(n) —— 很快,只遍历一遍数组
💾 空间复杂度:O(n) —— 哈希表最多存储 n 个不同的前缀和

🧠 算法思想:前缀和 + 哈希表优化查找

核心思想:

我们定义一个“前缀和”(prefix sum),即从数组开头到当前位置的所有元素之和。

如果两个位置之间的前缀和差值为 k,那么这两个位置之间的子数组之和就是 k

例如:

prefixSum[i] = sum(nums[0..i-1])
prefixSum[j] = sum(nums[0..j-1])

如果 prefixSum[j] - prefixSum[i] == k
=> nums[i..j-1] 的和为 k

为了高效查找是否存在某个 prefixSum[i] = prefixSum[j] - k,我们使用一个哈希表来记录每个前缀和出现的次数。


🧾 代码逐行解析

var subarraySum = function(nums, k) {
    let count = 0, currentSum = 0;          
    const prefixSumCount = new Map();  
    prefixSumCount.set(0, 1);     
  • count:记录满足条件的子数组个数。
  • currentSum:当前累计的前缀和。
  • prefixSumCount:用 Map 存储前缀和及其出现的次数。
  • prefixSumCount.set(0, 1):初始化,表示前缀和为 0 出现了一次(对应空子数组)。
    for (const num of nums) {
        currentSum += num;       
  • 遍历数组,逐步累加得到当前的前缀和 currentSum
        const requiredPrefixSum = currentSum - k;
        count += prefixSumCount.get(requiredPrefixSum) ?? 0;
  • 我们想找是否存在一个之前的前缀和 s,使得 currentSum - s == k
  • 这等价于找 requiredPrefixSum = currentSum - k 是否已经存在。
  • 如果存在,则有若干个子数组的和是 k,数量就是这个值在哈希表中出现的次数。
        prefixSumCount.set(
            currentSum,
            (prefixSumCount.get(currentSum) ?? 0) + 1
        );
    }
  • 将当前前缀和加入哈希表,统计它出现的次数。
    return count;
};
  • 返回最终找到的满足条件的子数组个数。

📝  算法步骤回顾

  1. 初始化一个哈希表 prefixSumCount,记录前缀和出现的次数。
  2. 初始设置:prefixSumCount.set(0, 1),表示前缀和为 0 出现了一次。
  3. 遍历数组,逐个累加当前元素得到 currentSum
  4. 每次计算 requiredPrefixSum = currentSum - k
  5. 如果 prefixSumCount 中存在这个 requiredPrefixSum,说明存在以当前 currentSum 结尾、和为 k 的子数组。
  6. 将该次数加到 count 中。
  7. 更新 prefixSumCount 中当前前缀和的计数。

时间复杂度和空间复杂度

⏱️ 时间复杂度分析

  • 整个数组只遍历一次:O(n)
  • 每次操作(查找、插入)都在 Map 中进行,平均时间复杂度是 O(1)

总时间复杂度:O(n)

🧮 空间复杂度分析

  • 使用了一个 Map 来保存所有不同的前缀和及其出现的次数
  • 最坏情况下,前缀和各不相同,最多会有 n+1 个不同的前缀和(包括初始的 0)

总空间复杂度:O(n)


小白快速上手:.set() 和 .get() 是什么?(高手也可以温习一下)

这两个方法是 JavaScript 中 Map 对象的核心方法之一,用于操作键值对(key-value pairs)。

✅ Map 简介

Map 是一种数据结构,类似于对象,但它允许你使用任意类型作为键(包括对象、函数、NaN 等),而不仅仅是字符串或 Symbol。


🔹 1. .set(key, value)

📌 功能:

将一个键值对添加到 Map 对象中。如果键已经存在,则更新对应的值。

🧩 参数:

  • key:要存储的键(可以是任何类型)
  • value:与键相关联的值

🔄 返回值:

返回调用 .set()Map 实例本身,因此支持链式调用。

✅ 示例:

const map = new Map();
map.set('name', 'Alice');
map.set(1, 'number one');
map.set({ id: 1 }, 'object key');

// 链式调用
map.set('a', 1).set('b', 2).set('c', 3);

🔹 2. .get(key)

📌 功能:

根据指定的键从 Map 对象中获取对应的值。

🧩 参数:

  • key:要查找的键

🔄 返回值:

  • 如果找到了该键,返回对应的值;
  • 否则返回 undefined

✅ 示例:

const map = new Map();
map.set('name', 'Alice');

console.log(map.get('name')); // 输出: Alice
console.log(map.get('age'));  // 输出: undefined

🧪 特殊注意点(关于引用类型)

当你使用对象作为键时,.get() 只有在传入的是同一个引用地址时才会找到对应的值。

❗ 示例:

const map = new Map();

const key1 = { a: 1 };
const key2 = { a: 1 };

map.set(key1, 'value1');

console.log(map.get(key1)); // 输出: value1
console.log(map.get(key2)); // 输出: undefined

因为 key1key2 虽然内容一样,但它们是两个不同的对象,内存地址不同。

🧱 总结对比表格

方法描述参数返回值是否可链式调用
.set(key, value)添加或更新键值对key(任意类型),value(任意类型)当前 Map 对象✅ 是
.get(key)获取键对应的值key(任意类型)对应的值或 undefined❌ 否

✅ 推荐使用场景

  • 需要频繁增删查键值对
  • 键不是字符串而是对象、数字、布尔值等
  • 想要更好的性能和更清晰的语义
  • 在算法题中统计频率(如 LeetCode 常见题目)