这道题是第119场周赛Q3,LC竞赛分为1676。注意到数据量最大为30000,所以不能使用O(n^2)的方法。
方法一. 暴力方法(超时)
双循环暴力,用前缀和稍微优化了一下,代码如下:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
int res = 0;
nums.push_back(0);
reverse(nums.begin(),nums.end());
for(int i=1;i<nums.size();i++){
nums[i] += nums[i-1];
}
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
res += !((nums[i]-nums[j])%k);
}
}
return res;
}
};
没有优化的双循环要在每个循环里一个一个加,比用前缀和优化要慢不少。但是前缀和仍然是O(n^2),是超时的。
方法二. 前缀和加一点点数学方法(超过10.36%C++用户)
我们有模的性质:若 a mod c = b mod c,则 (a-b) mod c = 0,有了这个性质,我们假设前缀和 nums[i] 与 nums[j] 对 k 取模都为a,那么从 i+1 到 j 的这个子数组(nums[j] - nums[i])模 k 为0。也就是说,我们通过统计模 k 为 0,1,2,... ,k-1 的前缀和个数,就可以得到相应的子数组的个数。子数组的个数很好求,如果有n个前缀和模 k 相同,那么子数组就有 1+2+...+(n-1)个。复杂度降到了 O(n),本质上是把循环求前缀和之差的过程转化为了求等差数列和。
代码如下:
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
nums.push_back(0);
reverse(nums.begin(),nums.end());
for(int i=1;i<nums.size();i++){
nums[i] += nums[i-1];
if(nums[i]<0){
nums[i] = k+nums[i]%k;
}
else{
nums[i]%=k;
}
}
vector<int> res(k,0);
for(int i=0;i<nums.size();i++){
int a = nums[i] % k;
res[a]++;
}
int ans = 0;
for(int i=0;i<k;i++){
ans+= res[i] * (res[i]-1)/2;
}
return ans;
}
};
这里要记得处理一下负数求模的问题,这个问题有点烦,报了几次错。