
误区:为什么不能直接用滑动窗口?
很多做过子数组滑动窗口题的同学第一反应就是双指针滑动窗口。
✅ 滑动窗口适用前提:数组全部都是正数。窗口扩大总和一定变大,窗口缩小总和一定变小,存在单调性。
本题数组里面可以包含负数和 0!
一旦存在负数,向右扩大右边界,总和反而有可能变小;向左移动左边界,总和反而变大。总和不再单调递增,左右指针没有办法按照规则收缩窗口,滑动窗口失效,不能使用。
基础概念:前缀和
定义前缀和数组 preSum:
preSum[i]:数组前 i 个元素的累加和(nums[0] ~ nums[i‑1])
(preSum0=0
preSum1=nums0
preSum2=nums0+nums1
preSumi = sum_{x=0}^{i-1} numsx)区间 nums[j ... i‑1](下标 j 到 i‑1 的连续子数组)的和等于:
(preSumi - preSumj = k)
移项变形得到:
含义:当我们遍历到位置
i,当前前缀和是preSum[i];我们希望统计历史上有多少个前缀和的值正好等于preSum[i]‑k,每出现一次就代表找到了一段子数组,它的和等于 k。
算法思路(前缀和 + 哈希优化)
Python 完整代码
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt=defaultdict(int)
ans=s=0 #s为前缀和
for x in nums:
cnt[s]+=1
s+=x
ans+=cnt[s-k]
return ans
复杂度分析
- 时间复杂度:(O(n)),只遍历数组一遍,哈希表查询插入都是 O (1)
- 空间复杂度:(O(n)),最坏情况所有前缀和各不相同,哈希表保存 n 个记录