题目:
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2 输出:2
示例 2:
输入:nums = [1,2,3], k = 3 输出:2
解法1:暴力枚举
代码:
class Solution {
public int subarraySum(int[] nums, int k) {
int count = 0;
for (int i = 0; i < nums.length; i++) { // 枚举子数组的起始位置
int sum = 0;
for (int j = i; j < nums.length; j++) { // 枚举子数组的结束位置
sum += nums[j]; // 累加从i到j的子数组的和
if (sum == k) { // 如果子数组的和等于k
count++; // 计数器加1
}
}
}
return count; // 返回满足条件的子数组数量
}
}
关键点:
1.思路,外循环固定起始值,内循环累计后面所有的值,满足条件,计数器加一。
解法2:前缀和+哈希表
代码:
class Solution {
public int subarraySum(int[] nums, int k) {
// 使用前缀和 + map 统计,和为 key,次数为 value
Map<Integer, Integer> map = new HashMap<>();
int pre = 0; // 当前前缀和
map.put(pre, 1); // 初始化前缀和为 0 的情况
int count = 0; // 满足条件的子数组数量
for (int i = 0; i < nums.length; i++) {
pre += nums[i]; // 更新当前前缀和
// 先检查 pre - k 是否存在于 map 中
if (map.containsKey(pre - k)) {
count += map.get(pre - k); // 如果存在,说明有满足条件的子数组
}
// 再更新 map
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
return count;
}
}
关键点:
1.思路:每次循环都记录前缀和,如果前缀和减去K有值存在,那说明之前就有满足条件的子数组。把统计的次数累加既是结果。
2.相关知识:
1. 前缀和的概念
前缀和是指从数组的起始位置到当前位置的所有元素的和。用pre
表示当前的前缀和,那么对于任意位置i
,pre[i]
表示从数组的起始位置到位置i
的元素之和。
2. 子数组和的表示
假设我们有一个数组nums
,我们需要找到所有子数组的和等于k
。对于任意子数组nums[i:j]
(从位置i
到位置j
的子数组),其和可以表示为: sum(i:j)=pre[j]−pre[i−1] 其中:
-
pre[j]
是从数组起始位置到位置j
的前缀和。 -
pre[i-1]
是从数组起始位置到位置i-1
的前缀和。
如果子数组的和等于k
,则有: pre[j]−pre[i−1]=k 即: pre[i−1]=pre[j]−k
3. 为什么使用count += map.get(pre - k);
在代码中,我们用map
来存储每个前缀和pre
出现的次数。当我们计算到当前的前缀和pre
时,我们希望找到所有满足以下条件的前缀和pre[i-1]
: pre[i−1]=pre−k
-
map.get(pre - k)
的含义:-
map.get(pre - k)
表示在当前前缀和pre
之前,有多少个前缀和等于pre - k
。 -
每一个这样的前缀和
pre - k
都对应一个满足条件的子数组。
-
-
count += map.get(pre - k)
的作用:-
如果
map
中存在pre - k
,说明从这些前缀和到当前前缀和pre
之间的子数组的和等于k
。 -
因此,
map.get(pre - k)
的值表示满足条件的子数组的数量。 -
将这个数量加到
count
中,可以统计所有满足条件的子数组。
-