
我们可以一边计算前缀和,一边遍历前缀和。
在遍历 nums 之前,我们需要先统计 s[0]=0,即空前缀的元素和等于 0。往 cnt 中添加 cnt[0]=1。
js
var subarraySum = function (nums, k) {
const cnt = new Map()
cnt.set(0, 1) // s[0]=0 单独统计
let ans = 0,
s = 0
for (const x of nums) {
s += x
ans += cnt.get(s - k) ?? 0
cnt.set(s, (cnt.get(s) ?? 0) + 1)
}
return ans
}js
/**
* 计算数组中和为k的子数组的个数
* @param {number[]} nums - 输入的整数数组(可包含正负整数)
* @param {number} k - 目标子数组和
* @returns {number} - 和为k的子数组的总个数
* 算法思路:前缀和 + 哈希表(Map)优化,时间复杂度O(n),空间复杂度O(n)
*/
var subarraySum = function (nums, k) {
// 1. 初始化哈希表,用于存储前缀和出现的次数
// 键:前缀和的值,值:该前缀和出现的次数
const prefixSumCount = new Map()
// 2. 初始化结果计数器和当前前缀和
let result = 0 // 记录和为k的子数组总数
let currentSum = 0 // 记录遍历过程中累加的前缀和
// 3. 遍历数组中的每个元素
for (const num of nums) {
// 3.1 先将当前的前缀和(未加当前num前)存入哈希表
// 如果该前缀和已存在,次数+1;不存在则初始化为1
prefixSumCount.set(currentSum, (prefixSumCount.get(currentSum) ?? 0) + 1)
// 3.2 累加当前元素,更新前缀和
currentSum += num
// 3.3 核心逻辑:查找是否存在前缀和 = currentSum - k
// 如果存在,说明这两个前缀和之间的子数组和为k,次数累加到结果中
// ?? 0 处理前缀和不存在的情况(此时贡献0次)
result += prefixSumCount.get(currentSum - k) ?? 0
}
// 4. 返回最终统计的子数组个数
return result
}要统计的是「当前位置之前的前缀和」(j < i),因此必须先把更新前的前缀和(代表「之前的状态」)存入哈希表,再更新前缀和。
初始值作用:哈希表初始存入 {0:1} 是为了处理「从数组第一个元素开始的子数组」(比如示例中的 [3]),确保这类情况能被正确统计。
我们可以一边计算前缀和,一边遍历前缀和。
在遍历 nums 之前,我们需要先统计 s[0]=0,即空前缀的元素和等于 0。往 cnt 中添加 cnt[0]=1。
在同一轮循环中,先把 s[i−1] 加入哈希表,再根据 s[i] 更新答案。
这样写无需初始化 cnt[0]=1。
js
var subarraySum = function (nums, k) {
const cnt = new Map()
let ans = 0,
s = 0
for (const x of nums) {
cnt.set(s, (cnt.get(s) ?? 0) + 1)
s += x
ans += cnt.get(s - k) ?? 0
}
return ans
}- 你现在走到了数组的第
i步,手里的总钱数是s(当前前缀和); - 你想知道 “从哪一步开始,到现在赚了
k块钱”; - 那只需要看 “之前有没有哪一步,手里的钱数是
s - k”—— 如果有,说明从那一步到现在,刚好赚了k块; cnt就是记录 “之前每一步手里有多少钱、出现过多少次” 的账本,查s-k就是翻账本找对应的记录。