Problem: 523. 连续的子数组和
暴力的方法就是求出前缀和数组,然后枚举每个区间和,找到长度大于2且整除k的子区间,但是会超时.
暴力超时
//枚举左端点
for(int i = 0 ; i另一种方法是利用同余性质 ,
同余定理:如果两个整数m、n满足n-m能被k整除,那么n和m对k同余
假设我们的目标是求得
S
[
j
]
−
S
[
i
−
1
]
S[j]-S[i-1]
S[j]−S[i−1](区间 [i,j]的和)是满足整除k,
则
S
[
j
]
−
S
[
i
−
1
]
%
k
=
=
0
S[j]-S[i-1] \% k ==0
S[j]−S[i−1]%k==0 ,即存在一个整数n ,满足
S
[
j
]
−
S
[
i
−
1
]
=
n
∗
k
S[j] -S[i-1] = n*k
S[j]−S[i−1]=n∗k
两边同时除以 k ,
S
[
j
]
k
−
S
[
i
−
1
]
k
=
n
\frac{S[j]}{k} -\frac{S[i-1]}{k} = n
kS[j]−kS[i−1]=n ,要使得n是整数,则
S
[
j
]
%
k
=
=
0
S[j] \%k ==0
S[j]%k==0 ,
S
[
i
−
1
]
%
k
=
=
0
S[i-1] \%k ==0
S[i−1]%k==0 。
我们使用一个 哈希表存储前缀和对于 k的余数,第一次出现的就是左端点,第二次出现的就是右端点。
O ( n ) O(n) O(n)
O ( n ) O(n) O(n)
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
// (a-b) % k == 0
// a%k == b%k
int m = nums.size() ;
if(m < 2 ) return false ;
// 哈希表维护的 key : 余数 ,value :前缀和对应的下标
unordered_map<int,int> mp ;
mp[0] = -1 ; // 前缀和为0的 下标为 -1
vector<int> perSum(m+1) ;
for(int i = 1 ; i <=m ; i ++ ) perSum[i] = perSum[i-1] + nums[i-1] ;
// s的下标取值 : [1,m]
int presum = 0 ;
for(int j = 1 ; j<=m ; j++ ) {
// 取出前缀和的余数
int r = perSum[j]%k ;
if(mp.count(r)) {
// 如果前缀和的余数存在,取出作为左端点
int i = mp[r] ;
if( j-i-1>=2 ) {
return true ;
}
}else{
mp[r] = j-1;
}
}
return false ;
}
};
空间上还能优化,将求前缀和的过程省略,边求前缀和,边存哈希表的值。