Leetcode:974. 和可被 K 整除的子数组 --前缀和,哈希,同余定理

题目链接:.和可被 K 整除的子数组
题目:

Leetcode:974. 和可被 K 整除的子数组 --前缀和,哈希,同余定理_第1张图片
侃侃:
先考虑一下暴力(必然是不行的),通过暴力开拓思路,通过暴力来个双循环进行计算每一段的和,然后进行统计。

	int res = 0;
	for(int i = 0; i < A.length; i ++) {
		int sum = 0;
		for(int j = i; j < A.length; j ++) {
			sum += A[i];
			if(sum % K == 0) {
				res ++;
			}
		}
	}

在计算的时候,我们发现每次计算和的时候 都需要重新进行累加,显然这是一个不明智的选择,那有没有什么方法,不需要每次进行累加,还能计算出一段连续的和呢?
答案是有的 前缀和
我们要求某一段区间的和是否 % K == 0 ,只需要判断 (sum[r] - sum[l - 1]) % K 就可以了。

通过同余定理可以转换为: sum[r] % K = sum[l - 1] % K
所以我们之需要求出 sum[i] % K 的值就可以了,然后统计 每个 sum[i] % K 值的数量,根据排列组合进行累加就可以得出总数了。
注意:
1、因为数组中有负值,所以 sum[i] % K 后得到的值也有可能是 负值,但是负值不便于我们统计答案,就需要将负值和正值进行统一。
具体可以看这篇文章:将一个负数取模后转换为正数(同余定理)
2、用 哈希 进行统计时需要先把 0 放进去,因为sum[i] % K == 0 的区间都可以自成一个合法区间(具体看代码)。
Code:

class Solution {
    public int subarraysDivByK(int[] A, int K) {
		// 得出 A 数组的长度
        int length = A.length;
        // 前缀和,本人习惯于从 1 开始,避免数组越界
        int[] preSum = new int[length + 1];
        int[] b = new int[length + 1];
        // 统计 % K 后相同的值
        Map<Integer,Integer> maps = new HashMap();
		// 便于后续的统计( sum[i] % K == 0 表示 1 ~ i 这个区间满足条件)
        maps.put(0,1);
        int res = 0;
		// 复制
        for(int i = 0; i < length; i ++) {
            b[i + 1] = A[i];
        }
		
        for(int i = 1; i < length + 1; i ++) {
        	// 前缀和
            preSum[i] = preSum[i - 1] + b[i];
            // Java 中 负数 % 正数 = 负数,为了进行统一计算,都转换为正数
            int mod = (preSum[i] % K + K) % K;
            // getOrdefault(),查询是否有 key 对应的 Value,有则返回对应的值,
            // 没有则返回默认值(可以自己设定)
            maps.put(mod,maps.getOrDefault(mod,0) + 1);
        }
		// 利用 Entry 获取一个键值对(具体可以在我的博客中进行查看,有具体的讲解)
        for(Map.Entry<Integer,Integer> entry : maps.entrySet()) {
        // 根据排列组合,统计总数
            res += (entry.getValue() * (entry.getValue() - 1)) / 2;
        }

        return res;

    }
}

你可能感兴趣的:(Leetcode,Algorithm)