题目来源:题目
题目
给定一个整数数组 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
思路
思路一(暴力解法)
遍历所有的子数组,判断每个子数组的和是否可以整除K.时间复杂度太高。
思路二(前缀和+哈希表+边遍历边计算)
求子数组的问题应该常考虑前缀和的解法。
前缀和即为前n个元素的和。例如用p(n) = p(0) + p(1) + … + p(n).所以子数组[i,j]的和就可以表示为p(j) – p(i – 1).
判断子数组[i,j]的和是否可以整除k,即判断(p(j) – p(i – 1) % k == 0是否成立,有因为同余定理。判断p(j) % k == p(i – 1) % K是否成立。
所以我们可以用hashmap记录前缀和模k的余数出现的次数,即用余数做key,它对应出现的次数为value,每次记录到p(j) % k的时候,判断此时hashmap中键为p(j) % k的值是多少。
思路三(前缀和+哈希表+排列组合)
上述的思路是在遍历计算前缀和时候就计算个数,我们可以一次性给哈希表赋完值之后用排列组合的方式计算个数。
例如余数为1的个数有n个,则n个里面可以两两配对,即余数为1的有n*(n – 1) / 2个子数组,所以对hashmap遍历,判断每种余数的子数组个数即可。
代码
思路二代码
public static int subarraysDivByK(int[] A, int K) {
Map<Integer,Integer> record = new HashMap<>();
record.put(0,1);
int sum = 0,ans = 0;
for(int ele : A){
sum += ele;
int moudle = (sum % K + K) % K;
int sam = record.getOrDefault(moudle,0);
ans += sam;
record.put(moudle,sam + 1);
}
return ans;
}
思路三代码
public static int subarraysDivByK(int[] A, int K) {
Map<Integer,Integer> record = new HashMap<>();
record.put(0,1);
int sum = 0,ans = 0;
for(int ele : A){
sum += ele;
int moudle = (sum % K + K) % K;
int sam = record.getOrDefault(moudle,0);
record.put(moudle,sam + 1);
}
for(Map.Entry<Integer,Integer> entry : record.entrySet()){
ans += (entry.getValue() * (entry.getValue() - 1)) / 2;
}
return ans;
}
总结
暴力算法的时间复杂度为O(n^2),不满足题目要求。
思路二的时间复杂度为O(n),空间复杂度为O(min(N,K))。
思路三的时间复杂度为O(n),空间复杂度为O(min(N,K))。
求子数组的问题多想想前缀和,将O(n^2)时间复杂度降到O(n).