丑数的思想

前言

这同样是算法思想的一篇总结篇,由面试问题得来。

在leetcode上有这样一道题目:求取第n个丑数。质因子只有2,3,5的数称为丑数,比如4(2*2),9(4*2),10(2*5),14(2*7),18(2*9)…….

题目连接即解法戳这里

解法1:
暴力就不说了。


解法2:
仔细想一想,这里面所有的丑数都是由前面旧的丑数和(2或3或5)构成的。假设之前有个丑数序列为a[1],a[2],a[3],难点在于,你不知道下一个丑数,是由a[1]*5,a[2]*3,a[3]*2中哪一个,以及,a[1] * 2 或 a[1]*3或a[1]*5的乘积有没有用过。所以,维持了3个类似指针的变量p2,p3,p5,分别记录没有与2,3,5相组合的第一个位置。p2=1时,如果a[p2]*2已经用过了,那么p2++,就是说a[1]*2已经用过了,p2指向2,下次2要和a[2]进行组合了。p3,p5同理,这样就可以得到代码。下一个丑数d,一定是由a[p2]*2,a[p3]*3,a[p5]*5这些从来没有用过的代码组成的,这样就得到了核心代码。

num=1;
ugly[0]=1;  
while(num<index)  
{             
//肯定是由下面这3个数中最小的组成下一个丑数。
    int new2=ugly[idx2]*2;  
    int new3=ugly[idx3]*3;  
    int new5=ugly[idx5]*5;  
    int tempVal=min(min(new2,new3), new5);  
    //找到是谁称为下一个丑数,找到了说明当前位置已经用过了,++;
    if(tempVal==new2) idx2++;  
    if(tempVal==new3) idx3++;  
    if(tempVal==new5) idx5++;  
        ugly[i]=tempVal;  
     num++;  
 }   
return ugly[index-1];  

丑数的升级版本。
这是某个大公司出过的一道题目。也是leetcode上面的原题,具体题目题号我已经忘记了,找到这个题号再来补充吧。
给你两个整数数组a[m],b[n],每次从a[m]中取一个数a[i],b[n]中取一个数b[j],得到a[i]*b[j],得到前k个最小的a[i]*b[j]。
这个题目就是上面丑数的变形,上面的题目只有2,3,5这三个数组成,而这里是由两个序列a[m],b[n]组成。上一题中,2,3,5可以无限使用,比如丑数72(2*2*2*3*3),而这个题,每次只能由a[i]*b[j]组成,不存在无数次使用的情况。但是思路是一样的,即维护m个指针p[m],p[i]指向当前第一个没有与a[j]使用过的位置。
核心代码如下。

int p[m];
for(int i=0;i<k;i++){
    int minn = MAXN;
    for(int j=0;j<m;j++){
        if(minn>a[p[j]]*b[j]){
            minn = a[p[j]]*b[j];
            minpos = j;
        }
    }
    p[minpos]++;
//  for(int j=0;j<m;j++){
//      if(minpos==j){
//          p[j]++;
//      }
//  }
}

但是其实这个题目还是可以简化,就是利用堆的思想。
上面的想法中,每次从数组p的所有m个数据中,找出最小的,然后再把最小的位置p[minpos]++。这样太过浪费,用堆的思想。
求前k个最小的a[i]*b[j],将a[0]*b[j](j从0到m-1),先全部放到小顶堆中。小顶堆的根root一定是下一个符合要求的数。假设当前小顶堆为根root为a[p[i]]*b[j]。去掉root,把p[j]++,把a[p[j]]*b[j]加入到小顶堆中(即把堆顶相邻的一个数字新加到堆中),维护堆。简单起见,这里可以使用优先队列来实现(优先队列就是用堆来维护的,优先队列中的数据并不是全部有序的,只是队头是最大或最小的),维护的复杂度为log(n),这样复杂度就将为mlog(n)了(或者nlog(m))
这里给出具体的题目,并且附上AC的代码
K-th Smallest Prime Fraction


    Comparator<Node> comparator = new Comparator<Node>() {
        @Override
        public int compare(Node o1, Node o2) {
            if (o1.node > o2.node)
                return 1;
            else {
                return -1;
            }
        }
    };
    class Node {
        int x;
        int y;
        double node;

        public Node(int x, int y, double node) {
            this.x = x;
            this.y = y;
            this.node = node;
        }
    }

    public int[] kthSmallestPrimeFraction(int[] A, int K) {
        Queue<Node> queue = new PriorityQueue<>(comparator);
        int len = A.length;
        int[] b = new int[A.length];

        for (int i = 0; i < A.length; i++) {
            b[i] = A[A.length - i - 1];
        }
        for (int i = 0; i < A.length; i++) {
            queue.add(new Node(i, 0, 1.0 * A[0] / b[i]));
        }
        Node ans = null;
        while (K-- != 0) {
            Node node = queue.poll();
            ans = node;
            if (node.y + 1 < len) {
                Node tN = new Node(node.x, node.y + 1, 1.0 * A[node.y+1] / b[node.x]);
                queue.add(tN);
            }
        }
        return new int[]{A[ans.y],b[ans.x]};
    }

以这个题目为例,举个例子。1 2 3 5,找出第3个小的有理数。

a[i]/b[j]1235
11/1
21/2
31/3
51/5

先把1/1,1/2,1/3,1/5折四项加进去,维护队,堆顶为1/5,最小的就是1/5,去掉1/5,把1/5右边的2/5加进去。变成这样

a[i]/b[j]1235
11/1
21/2
31/3
52/5

再维护堆,堆顶变成1/3,第二小的就是1/3,把1/3从堆中取出来,把1/3右边的2/3放到堆中,就变成了。

a[i]/b[j]1235
11/1
21/2
32/3
51/5

如此往复,一直到拿到第k小的有理数。注意,当到达5/5的时候,说明已经满了,不再添加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值