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;
    }
    
  • 相关阅读:
    CodeForces 219D Choosing Capital for Treeland (树形DP)
    POJ 3162 Walking Race (树的直径,单调队列)
    POJ 2152 Fire (树形DP,经典)
    POJ 1741 Tree (树的分治,树的重心)
    POJ 1655 Balancing Act (树的重心,常规)
    HDU 2196 Computer (树形DP)
    HDU 1520 Anniversary party (树形DP,入门)
    寒门子弟
    JQuery选择器(转)
    (四)Web应用开发---系统架构图
  • 原文地址:https://www.cnblogs.com/FannyChung/p/chou-shu.html
Copyright © 2011-2022 走看看