【leetcode】974. 和可被 K 整除的子数组 【绝对能懂!】

【leetcode】974. 和可被 K 整除的子数组

题目描述:

给定一个整数数组 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

我的想法很简单,先找一个元素的子数组,再找两个连续元素的子数组,最后找length-1个连续元素的子数组,多么单纯,多么naive ,多么多的…for循环:

//O(n^3)
class Solution {
    public int subarraysDivByK(int[] A, int K) {
       int nums =0;
        int length =A.length;
        //确定组合个数
        for(int pattern=1;pattern<=length;pattern++){
            //确定每组起始位置
             for(int index=0;index<=length-pattern;index++){
                int sum = 0;
                //相加相邻组合个数个元素,也就是确定每组的结束位置
                for(int l=0;l<pattern;l++){
                    sum += A[index+l];
                }
                //System.out.println(sum);
                //判断条件符合
                if(sum%K==0){
                    nums++;
                }
            }
        }
        return nums;
    }
}

前面输入还好,直到我发现了这个

在这里插入图片描述

(超时警告.jpg!)

WTF? 你确定不是来捣乱的吗?! 这tm是人做的题吗?!

woc!我不干了!我尥蹶子了!我…我再看看吧…做不出来积分不都没了吗💩

这,这么多0,光分子集都得好一会吧 ? 这左下角的1000咋这么卑微呢…

算了算了,瞄一眼题解…(数学渣的卑微😐)

嗯,直接宣布暴力法死亡还行…

官方解法:
//O(n)
class Solution {
    public int subarraysDivByK(int[] A, int K) {
        Map<Integer, Integer> record = new HashMap<>();
        record.put(0, 1);
        //sum,也就是p[i],ans:子数组数
        int sum = 0, ans = 0;
        for (int elem: A) {
            sum += elem;
            // 注意 Java 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
            int modulus = (sum % K + K) % K;
            //getOrDefault:如果存在这个key就返回这个key对应的value,没有就返回默认值
            //same同来存储此modulus出现的次数
            //same赋值的位置决定了只有当modulus有第二次或二次以上相同的时候same数值才会被ans存入
	        //(只有一次存在的modulus无法构成符合的子集);
	        //且子数组数等于之前的modulus出现的数值
            int same = record.getOrDefault(modulus, 0);
   
            ans += same;
            //此modulus出现次数+1
            record.put(modulus, same + 1);
        }
        return ans;
    }
}

使用前缀和散列表,前缀计算数值,散列表统计。

令 P[i]=A[0]+A[1]+…+A[i](这就是前缀和)。

那么每个连续子数组的和 sum(i,j),就可以写成p[j]-p[i] (i>j).

此时,判断子数组的和能否被 K整除就等价于判断 (P[j]−P[i−1])%K==0.根据同余定理( a%m==b%m,则称 a≡b (mod m) ) ,也就等价于判断p[j]%k == p[j-1]%k.

所以要判断连续子数组个数,就等于到每个节点(A[i])时modulus出现次数之和。

  • 如果不明白为什么统计次数就可以得到子数组个数,我们可以画个图

不同颜色代表不同的次数

你可以看出,每次增加第n个节点,总子数组数就增加n-1个

所以假设我们有4个相等的modulus,那么总数就是1+2+3=6
用 排 列 组 合 表 示 也 就 是 C 4 2 个 用排列组合表示也就是C_{4}^{2}\textrm{}个 C42

  • 关于为何要record.put(0, 1);?(结合后面的例子更好理解)

因为“子数组和%K==0”时比较特别,第一次出现“子数组和%K==0”时,这个子数组就满足题目要求了,不像其他的(如:子数组和%K==1)要出现一次以上。

你可以想象成子数组为空的时候子数组和为0,这时候满足子数组和%K==0,所以在还没有遍历之前就已经有了一种情况了,但是这种情况不算在结果(result)里面。

为了更清楚这个算法做了什么,举个例子看一看:

输入值:[4,5,0,-2,-3,1] 5

结果:

modulus:same
4:0
ans:0
4:1
ans:1
4:2
ans:3
2:0
ans:3
4:3
ans:6
0:1
ans:7
________________________
result:7
  1. 可以注意到,当“modulus=4”第二次出现的时候,“ans”才为1,所以modulus必须出现2次或2次以上才会被加入计数。

  2. 例子最后出现了“modulus=0”的情况,因为其本身(p[i],也就是集合{0…i})就符合题目规则,所以需要提前加1,否则就不会加入计数。(另外,就算提前加1,如果输入中不存在“modulus=0”这种情况,那也不会在“ans”中多加1)

哎,我理解了一下午才算搞懂,这也许就是中等难度吧!

P.S :头发又秃了

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值