题目概述
给你一个整数数组 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
当我们遍历到当前索引 并计算出前缀和 时,我们只需要知道之前有多少个索引 处的前缀和恰好等于 ,这些索引的数量就是以 结尾、且和为 的子数组的个数。
-
mp(哈希表 Map): 用于存储 前缀和 及其出现的 次数。key: 某个前缀和的值(即 )。value: 该前缀和出现的次数。
-
pre: 当前遍历到的前缀和 。 -
count: 最终结果,和为 的子数组的总个数。
初始化与边界条件
-
mp.set(0, 1): 处理以数组第一个元素开始的子数组。- 如果子数组从索引 开始,即
nums[0..i],那么它的和是 。 - 公式为
Sum(0, i) = S[i] - S[-1]。定义空前缀和 。 - 通过初始化
mp.set(0, 1),相当于提前记录了空前缀和 出现了一次。这样,当 时,mp.has(k - k) = mp.has(0) 就会找到这个 ,并正确地将计数加一。
- 如果子数组从索引 开始,即
算法通过循环遍历数组中的每一个元素 x,并在每一步执行两个主要动作:
查找目标前缀和并计数
if (mp.has(pre - k)) { count += mp.get(pre - k); }- 在计算出当前前缀和
S[i]后,立即去哈希表中查找目标前缀和 。 - 如果找到,说明之前有 个子数组的前缀和为 。那么,当前以 结尾、和为 的子数组就有 个,将 加到
count中。
更新当前前缀和的出现次数
if (mp.has(pre)) {
mp.set(pre, mp.get(pre) + 1);
} else {
mp.set(pre, 1);
}
- 将当前计算得到的 前缀和 () 及其出现次数记录或更新到哈希表中,供后续遍历时使用。
时间,空间复杂度
时间复杂度:
- 算法只进行了一次数组遍历。
- 哈希表(Map)的查找、插入和更新操作的平均时间复杂度均为 。
- 因此,总时间复杂度为线性的 。
空间复杂度:
最坏情况下,数组中所有前缀和都不同(例如递增数组),哈希表需要存储 个不同的前缀和以及初始的 。因此,空间复杂度为 。