首先题目中明确表示所求的子数组和连续、非空的,因此可以使用前缀和来对暴力的遍历进行简化。
为了直观地体现前缀和的作用,在不使用同余定理的前提下写出以下仅使用前缀和数组的方法,由于使用两重循环,因此该方法会超时。
class Solution {
public int subarraysDivByK(int[] A, int K) {
int len = A.length;
int[] prefix = new int[len];
int sum=0;
int res=0;
//对第一位特殊处理,因为prefix[-1]为0
prefix[0] = A[0];
for(int i=1; i<len; i++){
prefix[i] = sum+A[i] + prefix[i-1];
System.out.print(prefix[i] + ", ");
}
for(int i=0; i<len; i++){
//此处对prefix[-1]特殊处理
if( (prefix[i] -0) % K ==0){
res++;
}
for(int j=1; j<=i; j++){
if((prefix[i] - prefix[j-1]) % K ==0){
res++;
}
}
}
return res;
}
}
为了进一步简化上述代码,引入同余定理。如果两个前缀和的对K的余数相等,那么这两个前缀和之差所得到的子数组之和必定可以被K整除。因此我们只需要计算K的每个余数所对应的的前缀和的出现次数就可以得到子数组的数量。
class Solution {
public int subarraysDivByK(int[] A, int K) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int prefix=0;
int res=0;
for(int i=0; i<A.length; i++){
prefix = (prefix + A[i]) % K;
if(prefix<0)prefix+=K;
if(map.containsKey(prefix)){
res += map.get(prefix);
map.put(prefix, map.get(prefix)+1);
}else{
map.put(prefix, 1);
}
}
return res;
}
}
利用数组代替Map,能进一步提高代码效率。
class Solution {
public int subarraysDivByK(int[] A, int K) {
int[] map = new int[K];
map[0]++;
int prefix=0;
int res = 0;
for(int a : A){
prefix = (a+prefix)%K;
if(prefix<0)prefix+=K;
res+=map[prefix]++;
}
return res;
}
}