卡牌分组 给定一副牌,每张牌上都写着一个整数。
此时,你需要选定一个数字 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)