丑数系列

丑数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);
}
posted @ 2021-03-30 12:53  MaughamQ  阅读(182)  评论(0)    收藏  举报