力扣每天一题 : NO914 卡牌分组[JAVA]

卡牌分组 给定一副牌,每张牌上都写着一个整数。

此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:

每组都有 X 张牌。 组内所有的牌上都写着相同的整数。 仅当你可选的 X >= 2 时返回 true。
示例 1:

输入:[1,2,3,4,4,3,2,1] 输出:true 解释:可行的分组是 [1,1],[2,2],[3,3],[4,4]
示例 2:

输入:[1,1,1,2,2,2,3,3] 输出:false 解释:没有满足要求的分组。
示例 3:

输入:[1] 输出:false 解释:没有满足要求的分组。
示例 4:

输入:[1,1] 输出:true 解释:可行的分组是 [1,1]
示例 5:

输入:[1,1,2,2,2,2] 输出:true 解释:可行的分组是 [1,1],[2,2],[2,2]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

emmm,这个题的意思就是分组,说白了,就是求大公约数的问题。
所以第一想法就是
1.将数组里面的数分组,计算每一个数字出现的次数
2.求出所有次数的最大公约数
求最大公约数的这一步想了想,好像一时间没有什么特别好的办法。于是就暴力循环,从2到deck.length.
于是就有了一个耗时19ms 打败16%的人 的答案:

class Solution {
    public boolean hasGroupsSizeX(int[] deck) {
        // 数组只有一位时返回false
        if (deck.length == 1) {
            return false;
        }
        
        // 用一个hashMap计算数组中所有数字的个数,求出它们的最小公约数,若最小公约数是一个>=2的整数则为true
        Map<Integer,Integer> map = new HashMap<>();
        
        for (int i : deck) {
            if (map.containsKey(i)) {
                map.put(i, map.get(i) + 1); // 如果有这个值,则把value + 1
            }else {
                map.put(i,1); // 没有则放入map中,初始值为 1
            }
        }
        
        boolean result = false;
        for (int i = 2 ; i <= deck.length; i++ ) {
            for (Integer value : map.values()) {
                if (value % i == 0) {
                    result = true;
                } else {
                    result = false;
                    break;
                }
            }
            // 一轮循环下来为真即可返回
            if (result) {
                return result;
            }            
        }
        return result;
    }
}

自己回顾一下代码, 发现这种思路下很难再有什么实质上的优化.
于是膜拜了一下别人3ms的标答

class Solution {
    public boolean hasGroupsSizeX(int[] deck) {
        // 计数
        int[] counter = new int[10000];
        for (int num: deck) {
            counter[num]++;
        }
        // 迭代求多个数的最大公约数
        int x = 0;
        for(int cnt: counter) {
            if (cnt > 0) {
                x = gcd(x, cnt); 
                if (x == 1) { // 如果某步中gcd是1,直接返回false
                    return false;
                }
            }
        }
        return x >= 2;
    }
    
    // 辗转相除法
    private int gcd (int a, int b) {
        return b == 0? a: gcd(b, a % b);
    }
}

作者:sweetiee
链接:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/solution/3ms-jian-dan-java-fu-zeng-reducexie-fa-miao-dong-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

两个优化点,一个是用数组代替map作计数。
很聪明的把数值等价与数组的下标,遍历的时候
counter[num]++(++是+1后赋值给自身)

再有一个是,求最大公约数的地方采取的优化。
数学里求多个数最大公约数的方法叫做————辗转相除法。

所谓辗转相除,直接贴百度的一个证法

a可以表示成a = kb + r(a,b,k,r皆为正整数,且r<b),则r = a mod b
假设d是a,b的一个公约数,记作d|a,d|b,即a和b都可以整除d。
而r = a - kb,两边同时除以d,r/d=a/d-kb/d=m,由等式右边可知m为整数,因此d|r
因此d也是b,a mod b的公约数
假设d是b,a mod b的公约数, 则d|b,d|(a-k*b),k是一个整数。
进而d|a.因此d也是a,b的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证。

一句话总结,两个数的最大公约数 = 其中一个数 和 两个数相除的余数的 最大公约数。
即我们的递推公式: gcd(a,b) = gcd(a , a%b)
而出迭代的条件,就是余数为零,余数为零时的除数就是最大公约数。(这里不能理解的自己品品)

通过这种优化,就将双层循环简化成了一层循环,不需要再去“预设”一个公约数去爆破。每次循环中用迭代的方法求公约数,次数是可控的。
整个算法的时间复杂度大约是o(kn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值