zoukankan      html  css  js  c++  java
  • 丑数

    定义

    丑数的定义是指只包含质因数2、3、5的正整数。认为1是第一个丑数。1、2、3、4、5、6...
    广义的丑数不限定质因数为2,3,5,而是给定一个数组。

    丑数:判断给定的整数是不是丑数。
    很简单,直接判断是否只包含这三种因子。也就是当它们能够整除2的时候,就一直做除法。3、5同理。最后得到结果为1就是丑数。不然就是有其他因子。

    public boolean isUgly(int num) {
        if(num<=0){
            return false;
        }
        //判断是否只包含这三种因子
        while((num&1)==0){
            num>>=1;
        }
        while(num%3==0){
            num/=3;
        }
        while(num%5==0){
            num/=5;
        }
        return num==1;
    }
    

    第n个丑数

    丑数 II:找出第n个丑数。丑数序列是1、2、3、4、5、6、8、9...

    动态规划

    每个丑数都是由前面某个数x2或者x3或者x5得到的。保留三个指针,第一个指针指向2要乘以的“某个数”,第二个指针指向3要乘以的某个数。

    丑数序列是从小到大排列的,所以要对2、3、5乘以之后得到的结果作为候选值,来比较大小,选择最小的。如果最小值是2乘积得到的结果,则2对应的指针需要后移。可能同时存在2、3乘积得到的结果都是最小值,这时候他们的指针都要后移。
    CleanShot 2019-12-13 at 20.44.05@2x
    比如上图,当已有1、2、3、4、5,此时,i2指向2,代表2要取的乘数为res[2]=3,对应候选值23=6,3要取的乘数为res[1]=2,对应候选值32=6,5要取的乘数为res[1]=2,对应候选值为5*2=10.三个候选值中,最小值是6,且2和3都对应6,所以i2和i3同时向后走。

    public int nthUglyNumber(int n) {
        if(n<=0){
            return -1;
        }
        int factor2=0,factor3=0,factor5=0;
        int[] res=new int[n];
        res[0]=1;
        for(int i=1;i<n;i++){
            int candi2=res[factor2]*2;
            int candi3=res[factor3]*3;
            int candi5=res[factor5]*5;
            int min=Math.min(Math.min(candi2,candi3),candi5);
            res[i]=min;
            if(min==candi2){
                factor2++;
            }
            if(min==candi3){
                factor3++;
            }
            if(min==candi5){
                factor5++;
            }
        }
        return res[n-1];
    }
    

    通过在最小堆的堆顶取值得到最小值。并且如果有连续都是min则要一直取。
    每次遍历得到res[i]时,把res[i]和三个因子相乘得到的结果放入最小堆中。
    这样的时间复杂度为O(nlogn)。

    超级丑数

    变形的丑数:
    超级丑数:找第n个超级丑数。所有质因数都是质数列表primes数组(长度k)里的数。

    思路:类似上面丑数的解决方式,但是需要把三个元素扩展到对数组的处理。

    动态规划

    求res[i]时,是求k个质因数乘以自己的乘数得到的候选数的集合中的最小值。然后谁和最小值相等,则谁的指针后移。
    时间复杂度为O(N*k)

    对应上一题的用堆的解法,也是直接把求res[i],并把res[i]和所有prime的乘积放入堆中。

    动态规划+堆

    根据动态规划的解法,可以稍微优化是用最小堆来保存这k个数的候选数,拿出最小值,并把它对应的质因数下标后移。并且如果还有最小值,则继续后移。
    时间复杂度O(N x m x logk),m是连续能有多少个相等的。

    public int nthSuperUglyNumber(int n, int[] primes) {
        if(n<=0){
            return -1;
        }
        int m=primes.length;
        int[] res=new int[n];
        res[0]=1;
        int[] factors=new int[m];
        PriorityQueue<int[]> queue=new PriorityQueue<>((a,b)->(a[0]-b[0]));
        for(int i=0;i<m;i++){
            queue.add(new int[]{res[factors[i]]*primes[i],i});
        }
        for(int i=1;i<n;i++){
            int[] node=queue.poll();
            int min=node[0];
            int id=node[1];
            res[i]=min;//注意这个要放在前面来,res[++factors[id]]可能需要
            queue.add(new int[]{res[++factors[id]]*primes[id],id});
            while(!queue.isEmpty()&&queue.peek()[0]==min){
                node=queue.poll();
                id=node[1];
                queue.add(new int[]{res[++factors[id]]*primes[id],id});
            }
        }
        return res[n-1];
    }
    

    特殊的丑数,只要求能整除任意一个

    丑数 III:找到第n个丑数。这道题的丑数和之前的都不一样,”丑数是可以被a或b或c整除的正整数“。

    输入:n = 5, a = 2, b = 11, c = 13
    输出:10
    解释:丑数序列为 2, 4, 6, 8, 10, 11, 12, 13... 其中第 5 个是 10。

    结果在 [1, 2 * 109] 的范围内
    n, a, b, c <= 109
    这里能看到6能够整除2,但是6包含因子3并不在a、b、c中。所以只要求能整除,并不要求所有因子都在abc中。

    这里如果套用以前的做法,用i2代替res[i2],则会超时,因为n,abc的范围是<=109,求n的时候,需要O(N*3)的时间。

    用以前类似堆的做法:对每个i,都把i*a,b,c加入堆中。然后获得第n个(这里可以不用动态更新,所以可以完全加入之后再排序)。——时间复杂度也是O(NlogN),会超时。

    在O(N)都会超时的情况下,只能想到O(logN)了,所以可以尝试用二分查找。对于找到的mid,判断是不是第n个。
    找x是第几个丑数:就是找[0,x]有多少个数能被a或者b或者c整除。0~x有多少个数能被a整除意味着x/a有多少个。

    画个韦恩图,避免把同时是ab的倍数的重复计算。总共有:cnt=x/a+x/b+x/c-x/a/b-x/a/c-x/b/c+x/a/b/c
    注意这里不能用x/a/b代表x同时能够被a、b整除。因为当a=4,b=6,其实x只需要能整除12,就能整除a和b。所以应该求最小公倍数
    x/a+x/b+x/b-x/lcm(a,b)-x/lcm(b,c)-x/lcm(a,c)+x/lcm(a,b,c)

    找到这样的x后,x不一定是丑数。如果p是第n个丑数,x属于[p+min(a,b,c))(左闭右开的区间)都能满足x对应的cnt=n。所以二分法求得n对应的mid后,需要在[mid,mid-min(a,b,c))上找到能够%a或者b或者c的。这个方法时间当a、b、c都不小时,复杂度有点高

    第一个满足条件的

    找到满足条件的第一个mid即可。

    注意ab的最小公倍数有可能超出int范围,需要用long表示。

    public int nthUglyNumber(int n, int a, int b, int c) {
        long lcm_ab=getLCM(a,b);
        long lcm_ac=getLCM(a,c);
        long lcm_bc=getLCM(b,c);
        long lcm_abc=getLCM(lcm_ab,c);
        int min=Math.min(Math.min(a,b),c);
        int left=min;
        int right=2000000000;
        while(left<=right){
            int mid=((right-left)>>1)+left;
            long cnt=getCnt(mid,lcm_ab,lcm_ac,lcm_bc,lcm_abc,a,b,c);
            if(cnt<n){//右
                left=mid+1;
            }else if(cnt>n){//左
                right=mid-1;
            }else{//cnt==n
                if(getCnt(mid-1,lcm_ab,lcm_ac,lcm_bc,lcm_abc,a,b,c)<n){//第一个=
                    return mid;
                }else{//左
                    right=mid-1;
                    left=Math.max(left,mid-min+1);//这里能够稍微缩小一下left的范围
    
                }
            }
        }
        return -1;
    }
    
    private long getCnt(long mid,long lcm_ab,long lcm_bc,long lcm_ac,long lcm_abc,int a,int b,int c){//求[0,mid]有多少个数字能被abc整除
        return (mid/a+mid/b+mid/c-mid/lcm_ac-mid/lcm_bc-mid/lcm_ab+mid/lcm_abc);
    }
    
    private long getLCM(long a,long b){//求最小公倍数
        long gcd=getGCD(a,b);
        return a/gcd*b;
    }
    
    private long getGCD(long a,long b){
        if(a<b){
            long tmp=b;
            b=a;
            a=tmp;
        }
        while(b!=0){
            long mod=a%b;
            long chu=a/b;
            a=b;
            b=mod;
        }
        return a;
    }
    

    找到后直接计算

    之前提到“找到这样的x后,x不一定是丑数。如果p是第n个丑数,x属于[p+min(a,b,c))(左闭右开的区间)都能满足x对应的cnt=n。”其实mid-min(mid%a,mid%b,mid%c)就能得到比mid小,离mid最近的满足条件的数。

    public int nthUglyNumber(int n, int a, int b, int c) {
        long lcm_ab=getLCM(a,b);
        long lcm_ac=getLCM(a,c);
        long lcm_bc=getLCM(b,c);
        long lcm_abc=getLCM(lcm_ab,c);
        //System.out.println(lcm_ab+" "+lcm_ac+" "+lcm_bc+" "+lcm_abc);
        int min=Math.min(Math.min(a,b),c);
        int left=min;
        int right=2000000000;
        int mid=0;
        while(left<=right){
            mid=((right-left)>>1)+left;
            long cnt=getCnt(mid,lcm_ab,lcm_ac,lcm_bc,lcm_abc,a,b,c);
            //System.out.println(mid+":"+cnt);
            if(cnt<n){//右
                left=mid+1;
            }else if(cnt>n){//左
                right=mid-1;
            }else{//cnt==n
                break;
            }
        }
    
        mid=mid-Math.min(Math.min(mid%a,mid%b),mid%c);
        return mid;
    }
    
  • 相关阅读:
    android 微信(5.3)聊天UI的布局思考
    android 屏幕适配
    不同Activity之间的动画切换
    Freemarker 进行字符串截取
    如何使带背景图片的Button按钮中的文字居中偏上显示
    关于在IE-8下 button的背景图片不能正确显示的问题
    android Xmpp+openfire 消息推送 :SASL authentication failed using mechanism DIGEST-MD5
    Android运行出现“java.io.IOException: 您的主机中的软件放弃了一个已建立的连接。”
    计算机网络
    Java基础-3
  • 原文地址:https://www.cnblogs.com/FannyChung/p/chou-shu.html
Copyright © 2011-2022 走看看