974. 和可被 K 整除的子数组
20200527
难度:中等
题目描述
给定一个整数数组 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
Solution
看到子数组和——想到前缀和,和 560. 和为K的子数组类似
-
什么是前缀和?
数组 第 0 项 到 当前项 的 总和,于是有
preSum[i]=A[0]+A[1]+…+A[i]
数组某项,可以表示为相邻前缀和之差:
A[i]=preSum[i]−preSum[i−1]
叠加多项,得到
A[i]+…+A[j]=preSum[j]−preSum[i−1]
让
preSum[-1]
为 0,此时:A[0]+A[1]+…+A[j]=preSum[j]
-
这里的【前缀和】指的是:前j项和 mod K
元素之和能被 K 整除的子数组数目,也就是求i,j的组合,使得
第 i ~ j 项的和 mod K == 0
(pre[j]-pre[i-1]) mod K == 0
即
pre[j] mod K == pre[i-1] mod K
-
用哈希表存【前缀和】以及对应出现的次数
- key:前缀和mod K
- value:这个余数值出现了几次
方法一:哈希表 + 逐一统计
class Solution {
public int subarraysDivByK(int[] A, int K) {
Map<Integer, Integer> preSumFreq = new HashMap<>();
int modK = 0;
int count = 0;
int sum = 0;
preSumFreq.put(0, 1);//[前缀和 mod K = 0]已经出现 1 次
for(int num : A){
sum += num;
// modK = sum % K;
modK = (sum % K + K) % K;
if(preSumFreq.containsKey(modK)){
count += preSumFreq.get(modK);
}
preSumFreq.put(modK, preSumFreq.getOrDefault(modK, 0) + 1);
}
return count;
}
}
- 要注意是取余的求法,Java中负数取模时,当被取模数为负数时取模结果为负数,需要纠正,因为如果有负数的话会导致哈希表找不到正确的余数。
modK = (sum % K + K) % K;
这样就可以把求出来的负数模变成正数,取正余数。 - 负数参与的取模运算规则:先忽略负号,按照正数运算之后,被取模的数是正数结果就取正,反之取负。
方法二:哈希表 + 单次统计
方法一是在遍历的时候逐一累加答案。
方法二和上面的思路类似,只是最后再统计答案,用排列组合来计算。
哈希表中(key,value)键值对,key这个前缀和出现了value次,要从这value次中任取两次,分别作为子数组的头和尾,例如value=4次,也就是
(
4
2
)
=
6
\binom{4}{2} = 6
(24)=6
则当前value对应的符合要求的子数组个数为value*(value-1)/2
class Solution {
public int subarraysDivByK(int[] A, int K) {
Map<Integer, Integer> record = new HashMap<>();
record.put(0, 1);
int sum = 0;
for (int elem: A) {
sum += elem;
int modulus = (sum % K + K) % K;
record.put(modulus, record.getOrDefault(modulus, 0) + 1);
}
int ans = 0;
for (Map.Entry<Integer, Integer> entry: record.entrySet()) {
ans += entry.getValue() * (entry.getValue() - 1) / 2;
}
return ans;
}
}