题目描述
给你一个整数数组 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
暴力枚举(双重循环)
最直观的方法是枚举所有可能的连续子数组,计算它们的和并统计等于 k
的个数。
1 | public int subarraySum(int[] nums, int k) { |
复杂度分析
- 时间复杂度:
O(n²)
,其中n
是数组长度。外层循环遍历每个元素作为起始点,内层循环遍历从起始点开始的所有子数组。 - 空间复杂度:
O(1)
,只使用了常数级别的额外空间。
前缀和 + 哈希表
在做这道题之前,我们先看之前做过的两道题:
思路
利用 前缀和 技术优化计算过程:
- 计算数组的前缀和数组
prefix
,其中:prefix[0] = 0
,表示空数组的和,用于处理从第一个元素开始的子数组prefix[i + 1] = nums[0] + nums[1] + ... + nums[i](i ≥ 0)
- 对于任意子数组
nums[i..j]
(包含下标i
到j
),其和可表示为prefix[j + 1] - prefix[i]
- 问题转化为:寻找满足
prefix[j + 1] - prefix[i] = k
且0 ≤ i < j + 1 ≤ n
的索引对(i, j)
的数量 - 使用哈希表存储每个前缀和出现的次数,遍历时查询
prefix[j] - k
的出现次数并累加(这其实就是上面的两数之和)
1 | public int subarraySum(int[] nums, int k) { |
示例解析
以 nums = [1, 1, 1]
, k=2
为例:
- 前缀和数组:
prefix = [0, 1, 2, 3]
- 遍历过程:
p=0
:查找-2
→ 不存在,count=0
;更新map{0:1}
p=1
:查找-1
→ 不存在,count=0
;更新map{0:1, 1:1}
p=2
:查找0
→ 存在1次,count=1
;更新map{0:1, 1:1, 2:1}
p=3
:查找1
→ 存在1次,count=2
;更新map{0:1, 1:1, 2:1, 3:1}
- 返回结果:
2
常见问题解答
- 为什么需要
prefix[0] = 0
?
考虑子数组从第一个元素开始的情况:子数组nums[0..j]
的和应为prefix[j + 1] - prefix[0]
没有prefix[0]
就无法表示从数组开头开始的子数组 - 哈希表如何处理重复前缀和?当不同位置产生相同的前缀和时,哈希表记录出现次数。例如:
nums = [0, 0, 0]
,k=0
- 前缀和:
[0, 0, 0, 0]
- 遍历时:
p=0
:查找0
→ 初始不存在,count=0
;记录0:1
p=0
:查找0
→ 存在1次,count=1
;记录0:2
p=0
:查找0
→ 存在2次,count=3
;记录0:3
p=0
:查找0
→ 存在3次,count=6
;记录0:4
- 返回结果:6(正确对应6个子数组)
复杂度分析
- 时间复杂度:
O(n)
,其中n
是数组长度。遍历数组两次(计算前缀和和统计子数组数量)。 - 空间复杂度:
O(n)
,哈希表最多存储n + 1
个键值对。
总结
- 暴力枚举简单直接,适合小规模数据(
n ≤ 1000
) - 前缀和 + 哈希表是更高效的通用解法,时间复杂度
O(n)
,能处理较大规模数据(n ≤ 2×10⁴
) - 实际应用中优先选择方法二,其线性时间复杂度能有效应对常见场景
- 关键点在于理解前缀和定义和索引映射关系:子数组和 =
prefix[j + 1] - prefix[i]