🧩 题目
给你一个整数数组 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;
};
- 返回最终找到的满足条件的子数组个数。
📝 算法步骤回顾
- 初始化一个哈希表
prefixSumCount,记录前缀和出现的次数。 - 初始设置:
prefixSumCount.set(0, 1),表示前缀和为 0 出现了一次。 - 遍历数组,逐个累加当前元素得到
currentSum。 - 每次计算
requiredPrefixSum = currentSum - k。 - 如果
prefixSumCount中存在这个requiredPrefixSum,说明存在以当前currentSum结尾、和为k的子数组。 - 将该次数加到
count中。 - 更新
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
因为 key1 和 key2 虽然内容一样,但它们是两个不同的对象,内存地址不同。
🧱 总结对比表格
| 方法 | 描述 | 参数 | 返回值 | 是否可链式调用 |
|---|---|---|---|---|
.set(key, value) | 添加或更新键值对 | key(任意类型),value(任意类型) | 当前 Map 对象 | ✅ 是 |
.get(key) | 获取键对应的值 | key(任意类型) | 对应的值或 undefined | ❌ 否 |
✅ 推荐使用场景
- 需要频繁增删查键值对
- 键不是字符串而是对象、数字、布尔值等
- 想要更好的性能和更清晰的语义
- 在算法题中统计频率(如 LeetCode 常见题目)