原题链接🔗:和为 K 的子数组
难度:中等⭐️⭐️
题目
给你一个整数数组 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
题解
前缀和 + 哈希表方法
LeetCode 上的 “和为 K 的子数组” 问题要求找出一个数组中所有和为 k 的连续子数组的个数。这个问题可以通过使用前缀和加哈希表的方法来解决。
- 题解:
前缀和:首先计算出数组的前缀和,即到当前位置的元素和。
哈希表:使用一个哈希表来存储前缀和出现的次数。这包括了前缀和为0出现的次数,因为任何和为0的子数组都是由其他和为k的子数组通过减法得到的。
双指针(可选):对于更优的空间复杂度,可以使用双指针来避免存储所有的前缀和,而是只存储最近的前缀和。
滑动窗口(可选):与双指针类似,滑动窗口可以在不牺牲时间复杂度的情况下减少空间复杂度。
更新计数:当当前前缀和与 k 的差值在哈希表中存在时,说明找到了一个和为 k 的子数组。此时,更新计数器。
- 复杂度:
时间复杂度O(N),其中 N为数组的长度。我们遍历数组的时间复杂度为 O(N),中间利用哈希表查询删除的复杂度均为 O(1),因此总时间复杂度为 O(N)。
空间复杂度O(N),其中 N 为数组的长度。哈希表在最坏情况下可能有 N 个不同的键值,因此需要 O(N) 的空间复杂度。
- 代码过程:
- 初始化一个变量 prefixSum 为0,用于存储前缀和。
- 初始化一个空的哈希表 sumCount,键为前缀和,值为该前缀和出现的次数。
- 初始化计数器 count 为0,用于存储和为 k 的子数组的个数。
- 遍历数组:
- 对每个元素 nums[i],更新 prefixSum。
- 检查 prefixSum - k 是否存在于 sumCount 中,如果存在,count 加上 sumCount[prefixSum - k]。
- 更新 sumCount[prefixSum],如果 prefixSum 已经存在,增加它的计数,否则设置为1。
- 返回 count。
- 注意事项:
- 确保在更新哈希表时,正确地增加前缀和的出现次数,而不是简单地设置为当前索引。
- 在计算和为 k 的子数组个数时,需要考虑前缀和为0的情况,因为任何和为0的子数组都可以通过减去另一个和为k的子数组得到。
- c++ demo:
#include <vector>
#include <unordered_map>
#include <iostream>
using namespace std;
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> sumCount = { {0, 1} }; // 初始化哈希表,前缀和为0出现1次
int count = 0, prefixSum = 0;
for (int num : nums) {
prefixSum += num; // 更新前缀和
count += sumCount[prefixSum - k]; // 找到和为k的子数组的个数
sumCount[prefixSum]++; // 更新前缀和的出现次数
}
return count;
}
};
int main() {
Solution solution;
vector<int> nums = { 1, 2, 3 };
int k = 3;
int subarrayCount = solution.subarraySum(nums, k);
cout << "The number of subarrays with sum " << k << " is: " << subarrayCount << endl;
return 0;
}
- 输出结果:
The number of subarrays with sum 3 is: 2
前缀和
前缀和是数组中的一个概念,它表示数组从第一个元素到当前元素的和。具体来说,对于一个数组
nums
,其前缀和prefixSums
可以定义如下:
即,prefixSums[i]
是nums[0]
到nums[i]
的和。前缀和的应用
前缀和在处理涉及数组元素累积和的问题时非常有用,它可以在 O(1)
时间内查询任意两个索引之间的和,而不需要重新计算。以下是一些常见的应用场景:
- 查询区间和:快速计算数组中任意两个索引间的和。
- 优化某些类型的子数组问题:例如,查找和为特定值的子数组,或查找最大子数组和。
- 计算加权平均数:如果数组中的元素代表权重,前缀和可以用来快速计算加权平均数。
前缀和的计算
前缀和可以通过一次遍历数组来构建:
使用前缀和解决问题
一旦有了前缀和数组,就可以解决一些特定问题,例如:
- 查找和为 k 的子数组:使用两个指针扩展窗口,当窗口的和等于 k 时,记录解。
- 最小子数组和:查找使得连续子数组的和大于等于某个特定值的最小长度。
示例
假设有一个数组
nums = [1, 2, 3, 4]
,其前缀和数组prefixSums
将会是[1, 3, 6, 10]
。
prefixSums[0]
是nums[0]
的值,即 1。prefixSums[1]
是nums[0] + nums[1]
的值,即 1 + 2 = 3。prefixSums[2]
是nums[0] + nums[1] + nums[2]
的值,即 1 + 2 + 3 = 6。prefixSums[3]
是nums[0] + nums[1] + nums[2] + nums[3]
的值,即 1 + 2 + 3 + 4 = 10。通过前缀和,我们可以快速知道数组中任意区间的和,例如
nums[1] + nums[2]
的和就是prefixSums[2] - prefixSums[0] = 6 - 1 = 5
。