给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2 输出:2
示例 2:
输入:nums = [1,2,3], k = 3 输出:2
提示:
1 <= nums.length <= 2 * 10^4
-1000 <= nums[i] <= 1000
-10^7 <= k <= 10^7
提示 1
Will Brute force work here? Try to optimize it.
提示 2
Can we optimize it by using some extra space?
提示 3
What about storing sum frequencies in a hash table? Will it be useful?
提示 4
sum(i,j)=sum(0,j)-sum(0,i), where sum(i,j) represents the sum of all the elements from index i to j-1. Can we use this property to optimize it.
解法1: Brute force / 枚举 + 一维前缀和
class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
int[] prefix = new int[n + 1];
int ans = 0;
for (int i = 1; i <= n; i++) {
prefix[i] = prefix[i - 1] + nums[i - 1];
}
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
// sum(nums[i..j]) = prefix[j + 1] - prefix[i]
if (prefix[j + 1] - prefix[i] == k) {
ans++;
}
}
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n^2),n 是 数组 nums 的长度。
- 空间复杂度:O(n),n 是 数组 nums 的长度。
解法2:前缀和 + 哈希表优化
需要注意的是,从左往右一边更新一边计算的时候,已经保证了map[ prefixSum[ i ] − k ]里记录的 prefixSum[ j ] 的下标范围是 0 ≤ j ≤ i 。同时,由于prefixSum[i] 的计算只与前一项的答案有关,因此我们可以不用建立 prefixSum 数组,直接用 prefixSum 变量来记录 prefixSum[ i +1] 的答案即可。
此题和 LeetCode 930. 和相同的二元子数组-CSDN博客 完全相同。
class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
int ans = 0;
int prefixSum = 0;
// 存储每个前缀和出现的次数, key:前缀和, value: 出现次数
Map<Integer, Integer> map = new HashMap<>();
// 初始化前缀和为0的次数为1
map.put(0, 1);
// sum(nums[j..i]) == k, 即 prefixSum[i + 1] - prefixSum[j] == k
// 遍历i,对每个i找到满足 prefixSum[i + 1] - k == prefixSum[j]的prefixSum[j]的个数
for (int i = 0; i < n; i++) {
prefixSum += nums[i];
ans += map.getOrDefault(prefixSum - k, 0);
map.merge(prefixSum, 1, Integer::sum);
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n),n 是 数组 nums 的长度。
- 空间复杂度:O(n),n 是 数组 nums 的长度。最坏情况下,哈希表有 n 个不同的键值,因此需要 O(n) 的空间复杂度。
Q:为什么要把 map(0, 1) 也加到哈希表中?
A:要想把任意子数组都表示成两个前缀和的差,必须添加 map(0, 1) ,否则当子数组是前缀时,没法减去一个数。
Q:为什么这题不适合用滑动窗口做?
A:滑动窗口需要满足单调性,当右端点元素进入窗口时,窗口元素和是不能减少的。本题 nums 包含负数,当负数进入窗口时,窗口左端点反而要向左移动,导致算法复杂度不是线性的。