携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情
题目描述
给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
实例
输入:A = [4,5,0,-2,-3,1], K = 5 输出:7 解释: 有 7 个子数组满足其元素之和可被 K = 5 整除: [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
提示: 1 <= A.length <= 30000 -10000 <= A[i] <= 10000 2 <= K <= 10000
思路
我们按照正常人的思路走,首先你们一看到题目会想到暴力解法 那暴力解法怎么样呢?
时间复杂度O(n^3),
空间复杂度O(1).
确实空间的复杂度很小,因为他就需要一个变量就够了. 那么我们就要进行优化,这涉及到了子数组,我们考虑到前缀和的思路去优化.你可能要问为什么要考虑前缀和这个思路,咋考虑出来的? 前缀和数组:s[i]=a[1]+a[2]+a[3]+a[4]+....+a[i] 当你要计算(x,y)之间的数据和,就可以用s[y]-s[x]得出.所以就可以进行优化. 那我们来看看他的复杂度
时间复杂度O(n^2),少了一重循环(子数组里面数据的加和) 空间复杂度O(n),需要多余的数组来存储前缀和
代码如下
class Solution {
public:
int subarraysDivByK(vector<int>& A, int K) {
int sum=0;
int num=0;
int B[10000];
B[0]=0;
for(int i=1;i<=A.size();i++)
{
B[i]=B[i-1]+A[i-1];
for(int j=0;j<i;j++)
{
sum=(B[i]-B[j]);
if(sum%K==0)
{
num++;
cout<<sum<<endl;
}
}
}
return num;
}
};
其实我贴出原因是让你们看一看前缀和如何优化的.但是他的时间复杂度太高,会导致超时所以还要优化. 我们继续优化,(B[i]-B[j])%k==0就是i到j数组满足题意的子数组,经过 同余原理 进一步变形就是B[i]%K == B[j]%k.那么我们就只用统计前缀和的取余相同的出现次数,就可以知道符合题意的子数组个数. 我们用哈希表去处理. 注意:不同的语言负数取模的值不一定相同,有的语言为负数,对于这种情况需要特殊处理。
class Solution {
public:
int subarraysDivByK(vector<int>& A, int K) {
unordered_map<int, int> record = {{0, 1}};
int sum=0,ans=0;
for(int item:A)
{
sum+=item;
int num;
// 注意 C++ 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
num=(sum%K+K)%K;
// cout<<num<<endl;
if(record.count(num))
{
ans+=record[num];
}
++record[num];
}
return ans;
}
};
如果不清楚count,请查c++API帮助文档或者问度娘.
时间复杂度:O(N),其中 N 是数组 A 的长度。我们只需要从前往后遍历一次数组,因此总时间复杂度为 O(N)。 空间复杂度:O(\min(N, K)),即哈希表需要的空间。当 N≤K 时,最多有 N个前缀和,因此哈希表中最多有 N+1 个键值对;当 N > K 时,最多有 K 个不同的余数,因此哈希表中最多有 K 个键值对。也就是说,哈希表需要的空间取决于 N 和 K 中的较小值。
好了讲的差不多了.如果还有什么不懂请评论,有不对的地方请指出.