丑数系列
丑数III (LeetCode 1201题)
1.解题思路
给你四个整数:n 、a 、b 、c ,请你设计一个算法来找出第 n 个丑数。
丑数是可以被 a 或 b 或 c 整除的 正整数 。
示例 1:输入:n = 3, a = 2, b = 3, c = 5 输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。
1.1确定一个数的丑数因子数
题目是要找到至少能被a/b/c中一个数整除的第n个数
那么假设这个数为x,它之前能被数字a整除的数有x/a个,能被b整除的数有x/b个,能被c整除的有x/c个
这些能被整除的数字中,有重复的:比如能同时被ab整除的,同时被ac整除的,同时被bc整除的,还有同时被abc整除的
所以我们考虑所有情况:
| ID | 情况 | 释义 | 包含的丑数因子的数量(用lcm和下标表示最小公倍数) |
|---|---|---|---|
| 1 | 一个数只能被a整除 | 它一定是a的整数倍 | x/a-情况4-情况5 |
| 2 | 一个数只能被b整除 | 它一定是b的整数倍 | x/b-情况6-情况5 |
| 3 | 一个数只能被c整除 | 它一定是c的整数倍 | x/c-情况4-情况6 |
| 4 | 一个数能被a,c整除 | ac最小公倍数的整数倍 | x/lcm_ac-lcm_abc |
| 5 | 一个数能被a,b整除 | ab最小公倍数的整数倍 | x/lcm_ab-lcm_abc |
| 6 | 一个数能被b,c整除 | bc最小公倍数的整数倍 | x/lcm_bc-lcm_abc |
| 7 | 一个数能被a,b,c整除 | abc最小公倍数的整数倍 | x/lcm_abc |
总的情况数
sum=x/a+x/b+x/c-x/lcm_ab-x/lcm_ac-x/lcm_bc+x/lcm_abc
1.2 二分法
在知道了如何计算一个数包含多少个丑数因子后,只要用二分法不断缩小边界范围,然后找到一个数包含的丑数恰好是n即可
第一步:给定n和abc后,首先确定了目标数字的上下限,第n个丑数最小是min=Min(a,b,c),最大是最小那个数的n次方 max=POW(min,n)
第二步:设left=min,right=max
每次取中间数字mid=(left+right)/2
如果mid的丑数因子数量大于n,right=mid-1
如果mid的丑数因子数量小于n,left=mid+1
最终找到一个数的丑数因子数量是n
1.3 Note
注意,通过二分法计算的答案并非是最终答案,因为可以有很多数同时包含有n个丑数因子!
比如第n个丑数是X,那么[X,X + min(a,b,c))这个半开区间内的所有数都同时包含n个丑数因子,我们通过二分法得到的答案也随机分布于这个区间中。而实际上我们只需要得到该区间的左端即可。处理方法很简单:假设我们得到的临时答案是K(K∈[X,X + min(a,b,c))),那么K - min(K%a,K%b,K%c) = X.也就是只需要把临时答案减去其与a、b、c三者中取余的最小值即可!
2.求最大公因数(辗转相除法)
private long gcd(long a,long b){
if(a==0){return b;}
return gcd(b%a,a);
}
示例:求9和12的最大公因数
graph LR
A[gcd a=12,b=9] --> B[gcd a=9%12=9,b=12] --> C[gcd a=12%9=3,b=9] --> D[gcd a=9%3=0,b=3] --> E[return b=3]
3.求最小公倍数
两个数a,b的最小公倍数=a*b/最大公因数
private long lcm(long a,long b){
return a*b/gcd(a,b);
}
示例:求9和15的最小公倍数
最小公倍数=9*15/gcd(9,15)=9*15/3=45
4.Java代码

public int nthUglyNumber(int n, int a, int b, int c) {
if(a==1||b==1||c==1){return n;}
//两两组合的最小公倍数
long lcmAB=lcm(a,b);
long lcmAC=lcm(a,c);
long lcmBC=lcm(b,c);
//三个数的最小公倍数
long lcmABC=lcm(lcmAB,c);
int min=Math.min(Math.min(a,c),b);
long left=min,right=(long)Math.pow(min,n);
while(left<=right){
long mid=left+(right-left)/2;
long count=mid/a+mid/b+mid/c-mid/lcmAB-mid/lcmAC-mid/lcmBC+mid/lcmABC;
if(count==n){
left= mid;
break;
}else if(count<n){
left=mid+1;
}else{
right=mid-1;
}
}
return (int)(left-Math.min(Math.min(left%a,left%b),left%c));
}
//最小公倍数
private long lcm(long a,long b){
return a*b/gcd(a,b);
}
private long gcd(long a,long b){
if(a==0){return b;}
return gcd(b%a,a);
}

浙公网安备 33010602011771号