今天又一次倒在了前缀和上,leetCode原题
给定一个整数数组 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
我利用前缀和,逐一破解,可是超时了。说明思路没问题,就是时间复杂度高了
public int subarraysDivByK(int[] A, int K) {
if(A == null || K == 0) return 0;
int len = A.length;
int[] preSum = new int[len + 1]; // 前 i 项和存入数组
preSum[0] = 0;
int sum = 0;
for(int i = 0; i < len; i++){
sum += A[i];
preSum[i + 1] = sum;
}
int result = 0;
for(int i = 0; i < len; i++){
int j = i;
for(; j >= 0; j--){
int dif = preSum[i + 1] - preSum[j];
if(dif % K == 0){
result++;
}
}
}
return result;
}
看了官方的题解,它作了进一步的优化。
前缀和 (preSum[x] - preSum[y])% K == 0,那么一定可得
preSum[x]% K == preSum[y]% K,即两数同余。
所有统计出取余相同的前缀知,就能得到符合要求的子串。
用map来存统计信息,k:前缀和的余数,v:该余数出现的次数
public int subarraysDivByK(int[] A, int K) {
if(A == null || K == 0) return 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0,1); // 本身就整除,直接计为1
int sum = 0, res = 0;
for(int tem : A){
sum += tem;
int mod = ((sum % K) + K) % K; // sum % K 可能是负数,所以再加K,再取余
int value = map.getOrDefault(mod, 0);
// 假如余数为1的已经出现了两次,当第三次出现时,会多两个符合条件的子串
// 当第四次出现的时候,会多三个符合条件的子串
res += value;
map.put(mod, value + 1);
}
return res;
}
这个同余数做文章,我也想不到,可一旦知道了,我能用代码给实现出来。这就是逻辑方面的不足,不是代码能力的不足。你想不到,没什么,请相信,你并不孤独。