题目简介
提示
给你一个整数数组 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
解题思路
题目要求统计数组中和为 k 的连续子数组的个数,我们可以使用前缀和+哈希表的方法来解决,这是一种高效的 O(n) 解法。
思路分析
- 前缀和的概念:前缀和是指从数组起始位置到当前位置的所有元素之和
- 如果我们有前缀和 prefixSum[i] 表示 nums[0...i] 的和
- 那么任意子数组 nums[i...j] 的和可以表示为 prefixSum[j] - prefixSum[i-1]
- 利用哈希表优化
- 我们需要找到满足 prefixSum[j] - prefixSum[i-1] = k 的所有 (i, j) 对
- 问题转化为求 prefixSum[i-1] = prefixSum[j] - k 的所有(i, j)对
- 在遍历过程中,我们检查当前前缀和减去 k 的值是否在哈希表中出现过(i < j)
- 将当前前缀和加入到哈希表中
- 具体算法流程
- 初始化哈希表,记录前缀和出现的次数,初始{0: 1}(表示空前缀出现 1 次,这是算法继续执行的前提驱动条件)
- 遍历数组,累加前缀和
- 检查 prefixSum - k 是否在哈希表中,若存在更新相应计数
- 将当前前缀和加入到哈希表
代码实现
func subarraySum(nums []int, k int) int {
count := 0
sum := 0
preSum := make(map[int]int)
preSum[0] = 1
for _, num := range nums {
sum += num // 计算当前前缀和
if val, exists := preSum[sum-k]; exists {
count += val // 如果存在 sum-k 的前缀和,说明有 val 个子数组的和为 k
}
preSum[sum]++ // 更新前缀和的出现次数
}
return count
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组长度,只需要遍历一次数组
- 空间复杂度:O(n),最坏情况下哈希表需要存储 n 个不同的前缀和
- 前缀和的作用:通过前缀和,我们可以在O(1)时间内计算任意子数组的和
- 哈希表的作用:记录每个前缀和出现的次数,帮助我们快速找到满足条件的子数组
- 初始化preSum[0]=1的意义:表示空前缀和出现一次,处理从数组起始位置开始的子数组
这种方法巧妙地将O(n²)的暴力解法优化到了O(n),是一种典型的空间换时间的策略。
算法流程图
举例说明
让我们最后通过一个具体例子来解释算法的执行过程:
对于示例1: nums = [1,1,1], k = 2
| 步骤 | 当前元素 | 前缀和(sum) | 查找preSum[sum-k] | 计数(count) | 前缀和映射(preSum) |
|---|---|---|---|---|---|
| 初始 | - | 0 | - | 0 | {0:1} |
| 1 | 1 | 1 | preSum[1-2]不存在 | 0 | {0:1, 1:1} |
| 2 | 1 | 2 | preSum[2-2]=preSum[0]=1 | 1 | {0:1, 1:1, 2:1} |
| 3 | 1 | 3 | preSum[3-2]=preSum[1]=1 | 2 | {0:1, 1:1, 2:1, 3:1} |
最终结果为2,表示有2个和为k的子数组:[1,1]和[1]。