【LeetCode 560】和为K的子数组(前缀和+哈希)
题面:
思路:
看到连续非空序列之和,容易想到前缀和计算差分, n u m s nums nums 区间 [ j , i ] [j,i] [j,i] 的和即为: s u m ( i , j ) = p r e [ i ] − p r e [ j − 1 ] sum(i, j) = pre[i] - pre[j-1] sum(i,j)=pre[i]−pre[j−1]。
那么要怎么计算出有多少个区间符合题意 s u m ( i , j ) = = k sum(i,j) == k sum(i,j)==k 呢?
令 p r e [ i ] pre[i] pre[i] 表示 ∑ j = 0 i n u m s [ j ] \sum_{j=0}^i nums[j] ∑j=0inums[j],即 [ 0 , . . . i ] [0,...i] [0,...i] 区间内所有数的和。
这就转化为,对于每个点 i i i,要求有多少个 j , j ∈ [ 0 , i ] j,j\in[0,i] j,j∈[0,i] 是满足 s u m ( i , j ) = p r e [ i ] − p r e [ j − 1 ] = k sum(i,j)=pre[i]-pre[j-1]=k sum(i,j)=pre[i]−pre[j−1]=k,那么就会有多少对区间 [ j , i ] [j,i] [j,i] 对答案有贡献。
对于等式 p r e [ i ] − p r e [ j − 1 ] = k ( i ∈ [ 0 , n ) , j ∈ [ 1 , i ] ) pre[i] - pre[j-1] = k\ (i\in[0,n),j\in[1,i]) pre[i]−pre[j−1]=k (i∈[0,n),j∈[1,i]),可以化为: p r e [ j − 1 ] = p r e [ i ] − k pre[j-1] = pre[i]-k pre[j−1]=pre[i]−k。
这时候我们可以只统计合法的,即只统计前缀和为 p r e [ i ] − k pre[i]-k pre[i]−k 的 p r e [ j ] pre[j] pre[j] 有多少个即可。(这时候就转化为力扣 hot 第一题 两数之和了)
写个哈希表 m p mp mp,以前缀和为 k e y key key,出现次数为对应的 v a l val val,记录 p r e [ i ] pre[i] pre[i] 出现的次数。然后从左往右更新 m p mp mp 并统计贡献,那么当前点 i i i 的贡献 m p [ p r e [ i ] − k ] mp[pre[i]−k] mp[pre[i]−k] 可在 O ( 1 ) O(1) O(1) 时间内得到。最后的答案即为:以所有下标 i i i 为区间结尾的贡献之和。
注意: 因为已经从左往右遍历更新 m p mp mp 了,所以能保证在当前点 i i i 下统计的 p r e [ j ] pre[j] pre[j] 都是在 i i i 之前的,即 j ∈ [ 0 , i ] j\in[0,i] j∈[0,i],且每个前缀和 p r e [ i ] pre[i] pre[i] 只和上一个 p r e [ i − 1 ] pre[i-1] pre[i−1] 有关,故拿一个临时变量记录 p r e [ i − 1 ] pre[i-1] pre[i−1] 即可得到当前 p r e [ i ] pre[i] pre[i]。
代码:
int subarraySum(vector<int>& nums, int k) {int cnt = 0, pre = 0;unordered_map<int, int> mp;//考虑pre[i]==k的情况,即pre[j-1]=pre[i]-k=0//所以可将mp[0]初始化为1,当然,也可以直接判断 pre==k?++cnt:next()mp[0] = 1; for(const auto& x : nums) {pre += x;if(mp.find(pre - k) != mp.end())cnt += mp[pre - k];//我们是后推入mp[pre]的,否则如果先 mp[pre]++,则当 k=0 时会出错mp[pre]++;}return cnt;
}