醜數的思想

前言

這同樣是算法思想的一篇總結篇,由面試問題得來。

在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] 1 2 3 5
1 1/1
2 1/2
3 1/3
5 1/5

先把1/1,1/2,1/3,1/5折四項加進去,維護隊,堆頂爲1/5,最小的就是1/5,去掉1/5,把1/5右邊的2/5加進去。變成這樣

a[i]/b[j] 1 2 3 5
1 1/1
2 1/2
3 1/3
5 2/5

再維護堆,堆頂變成1/3,第二小的就是1/3,把1/3從堆中取出來,把1/3右邊的2/3放到堆中,就變成了。

a[i]/b[j] 1 2 3 5
1 1/1
2 1/2
3 2/3
5 1/5

如此往復,一直到拿到第k小的有理數。注意,當到達5/5的時候,說明已經滿了,不再添加。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章