leetCode914. 卡牌分组-最大公约数

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

此时,你需要选定一个数字 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]

提示:
1 <= deck.length <= 10000
0 <= deck[i] < 10000


思路:
1.读题知道「相同的数被分在一组」,每一组除了数值相同以外,我们还关心一个属性:这个组里元素的个数,很容易想到应该统计元素个数;
2. 看示例分析如下几点要求。

  • 示例 2 :表示若不能使得每个组的中元素个数相同,直接返回 false。
  • 示例 3 :告诉我们:如果检测到某个组里元素只有 1 个,直接返回 false。
  • 示例 5 :[2, 2, 2, 2] 硬是被拆成了 2 组,为的是与组 [1, 1] 的元素个数相等。这对应了题目「每组都有 X 张牌」 的要求。

步骤:
1、遍历一次,统计每个数值的个数,如果某个数值只有 1 个,直接返回 false;
2、再看一下示例 5,想更一般化的情况,输入 [2, 2, 2, 2, 3, 3, 3, 3, 3, 3],其实也是符合题意的分组,2 有 44 个,3 有 66 个,相同的 2 和 3 都需要拆成 2 个一组,因此这里的 X = 2,很显然 22 是这两个组的元素个数的公约数。
考察核心:
为此,需要对所有的数值的个数,求公约数。只要能找到任意的公约数(要严格大于 1)都可以。为此,可以从 2 开始,依次用素数去判定它是不是所有数的约数。但其实我们是有现成的算法求最大公约数的,这个最大公约数只要严格大于 11 就行。

 public boolean hasGroupsSizeX(int[] deck) {
    	if (deck.length < 2) {
			return false;
		}
		//存储
    	HashMap<Integer, Integer> poke  = new HashMap<Integer, Integer>();
    	for (int i : deck) {
			if (poke.get(i) != null) {
				poke.put(i, poke.get(i)+1);
			}else {
				poke.put(i, 1);
			}
		}
		//若有个数小于2的值则false,并记录最小的牌组的个数; 
    	int min = Integer.MAX_VALUE;
    	for (Integer val: poke.values()) {
    		min = Math.min(min, val);
			if (val < 2) {
				return false;
			}
		}
    	//寻找最小牌组大于2的公约数
    	Set<Integer> conNum = new HashSet<Integer>();
    	for (int i = 2; i <= min; i++) {
			if (min % i == 0) {
				conNum.add(i);
			}
		}
		//若所有的牌号都能以最小牌堆的公约数分配,则true;
    	int eleCount = poke.size();
    	for (Integer con_i : conNum) {
    		int count = 0;
			for (Integer val: poke.values())  {
				if (val % con_i != 0) {
					break;
				}
				count++;
			}
			if (count == eleCount) {
				return true;
			}
		}
    	return false;
    }
    

总结:
时间复杂度:O(N) = 存储n + 遍历n + 寻找最小堆的公约数min + 判断公约数 m*count(min) >> 2n;
空间复杂度:O(N) = hashmap + set ;
结果一般;
执行用时 :16 ms, 在所有 Java 提交中击败了30.47%的用户
内存消耗 :42.4 MB, 在所有 Java 提交中击败了5.64%的用户

看更优秀的题解;

    public boolean hasGroupsSizeX1(int[] deck) {
    	if (deck.length < 2) {
			return false;
		}
    	//使用数组计数,依照提示牌号的范围为0~10000,
    	int []cnt = new int [10000];
    	for (int num : deck) {
			cnt[num]++;
		}
        // 先得到第 1 个数的个数,以避免在循环中赋值
    	int x = cnt[deck[0]];
    	// 遍历存储的空间
    	for (int i = 0; i < 10000; i++) {
			//牌堆为1,false
			if (cnt[i] == 1) {
				return false;
			}
			if (cnt[i] > 1) {
			//求当前牌堆与当前的最大公约数的最大公约数
				x = gcd(x, cnt[i]);
				// x = 1,即两个牌堆公共序列为1,即不满足x>=2
				if (x == 1) {
					return false;
				}
			}
		}
    	return true;
    }
	private int gcd(int a, int b) {
		if (b == 0) {
			return a;
		}
		return gcd(b, a % b);
	}

本题的核心考点:最大公约数

求两个整数的最大公约数的方法:「辗转相除法」/「欧几里德算法」。

我们做一个推导:不失一般性,假设 a > b且 b != 0 ,记整数 a和 b 的最大公约数是函数 gcd(a, b)。假设它们之间存在关系a=b×k+r.
这里 k 是倍数,是一个整数(k = a / b),r 是余数(r = a % b),也是一个整数。

用记号 |表示整除关系,例如 2 | 6。

看等式右边,根据定义 gcd(b,r)既是 b 的因数,也是 r 的因数,所以一定是 b 和 r 的线性组合的因数,故 gcd(b,r) | a,根据定义 gcd(b,r) | b,即 gcd(b,r) 是两个数 a、b的公因数,故 gcd(b,r) | gcd(a,b)。
事实上,等式中 a 和 r 是地位相当的,做个等价变形 r=a−b×k,根据上面同样的思路,看右边:gcd(a,b)|r,并且 gcd(a,b)∣b,故 gcd(a,b)∣gcd(b,r)。
两个整数互相可以整除,那么它们要么相等,要么互为相反数。事实上,由于正负数不是研究公约数问题的核心,很多资料都会强制规定公约数是正数。因此 gcd(a,b)=gcd(b,r)。

由于编程语言中提供了求余数运算,因此,这样的等式可以一直递归做下去:
gcd(a, b) = gcd(b, a % b) = ... = gcd(非零整数, 0)) = 非零整数
由于除数和余数是交替出现在等式中的,这个方法就被称之为「辗转相除法」。求解的过程显然是一个递归结构,并且递归终止条件是除数 b == 0。

引用
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/solution/qiu-jie-zui-da-gong-yue-shu-java-by-liweiwei1419/
作者:一切随心
引用:https://www.cnblogs.com/drizzlecrj/archive/2007/09/14/892340.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值