劍指Offer醜數問題

這是劍指第一次卡死個人題……記錄一下java

 

首先看題目:數組

把只包含質因子二、3和5的數稱做醜數(Ugly Number)。例如六、8都是醜數,但14不是,由於它包含質因子7。 習慣上咱們把1當作是第一個醜數。求按從小到大的順序的第N個醜數。ide

 

一開始的思路:測試

一開始,我就直接蠻力,就按照它的描述,從2到根號n一個個試,看看是否是它的因子,是的話是否是質數,是質數的話是否是2或者3或者5什麼的。spa

最後作出來,前面幾個數字都是能夠經過測試的,但提交就一直說我超時經過率爲0……而後本地測了下超時的用例1500——emmm確實是,算了半天都沒出來。指針

 

而後百度,百度百科上對醜數的判別:code

首先除2,直到不能整除爲止,而後除5到不能整除爲止,而後除3直到不能整除爲止。最終判斷剩餘的數字是否爲1,若是是1則爲醜數,不然不是醜數。
 
用這個思路又寫了個,從2開始往上+1,而後每一個都這樣判斷是否是醜數,好的仍是超時。(其實這個和個人蠻力同樣,只是個人蠻力比較噁心。)
 
 
最後沒辦法看討論,大多用的都是一個思路:
全部的醜數都是前面的那個醜數乘以2或者3或者5而來的。因此咱們能夠維護三個隊列和一個正式醜數序列:
連接: https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
來源:牛客網

(1)醜數數組: 1
乘以2的隊列:2
乘以3的隊列:3
乘以5的隊列:5
選擇三個隊列頭最小的數2加入醜數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(2)醜數數組:1,2
乘以2的隊列:4
乘以3的隊列:3,6
乘以5的隊列:5,10
選擇三個隊列頭最小的數3加入醜數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(3)醜數數組:1,2,3
乘以2的隊列:4,6
乘以3的隊列:6,9
乘以5的隊列:5,10,15
選擇三個隊列頭裏最小的數4加入醜數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(4)醜數數組:1,2,3,4
乘以2的隊列:6,8
乘以3的隊列:6,9,12
乘以5的隊列:5,10,15,20
選擇三個隊列頭裏最小的數5加入醜數數組,同時將該最小的數乘以 2,3,5 放入三個隊列;
(5)醜數數組:1,2,3,4,5
乘以2的隊列:6,8,10,
乘以3的隊列:6,9,12,15
乘以5的隊列:10,15,20,25
選擇三個隊列頭裏最小的數6加入醜數數組,但咱們發現,有兩個隊列頭都爲6,因此咱們彈出兩個隊列頭,同時將12,18,30放入三個隊列;

 而後我按照這個思路,本身實現了次,也遇到了不少問題……blog

我是真正地維護了三個隊列,而後每次把最新的醜數乘以2,3,5而後分別加到三個隊列中,而後取出最小的數字處理,從哪一個隊列中取出就哪一個隊列出隊。隊列直接用PriorityQueue,能夠直接得到最小值。隊列

遇到的問題是:get

  1. 我這個直接按照上面的思路作的話,到後面數字很大會溢出……而後溢出的話,好比一個很大的醜數*5溢出了,就變成一個負數而後之後的最小值就是這個負數了——個人解決方法是:重寫一個Compator,而後傳入PriorityQueue中去。

  2. 這還不夠,由於到了後面,可能出現一個隊列中的全部數字都小於0的狀況,因此在拿出數字後比較出最小的這一步也要考慮到負數的狀況。

看下個人這個思路的實現的代碼,已經經過測試:

import java.util.*;

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 0)return 0;
        if(index == 1)return 1;
        
        Queue<Integer> q2 = new PriorityQueue<Integer>(new NewComparator());
        Queue<Integer> q3 = new PriorityQueue<Integer>(new NewComparator());
        Queue<Integer> q5 = new PriorityQueue<Integer>(new NewComparator());
        
        int uglyNum = 1, count = 1, minTemp;
        while(true) {
            q2.add(uglyNum * 2);
            q3.add(uglyNum * 3);
            q5.add(uglyNum * 5);
            
            //下面的操做這麼麻煩是由於會有幾個隊列有着一樣的最小值的狀況,這時候都要讓它出隊
            minTemp = getMinOfThree(q2.peek(), q3.peek(), q5.peek(), count);
            
            if(q2.peek() == minTemp)q2.poll();
            if(q3.peek() == minTemp)q3.poll();
            if(q5.peek() == minTemp)q5.poll();
            
            uglyNum = minTemp;
            count++;
            if(count == index)return uglyNum;
        }
    }
    
    
    // 找三個數字中沒有溢出的最小值
    private int getMinOfThree(int a, int b, int c, int count) {

        if (a < 0 || b < 0 || c < 0) {// 存在溢出狀況,有元素小於0
            int[] temp = new int[3];
            temp[0] = a;
            temp[1] = b;
            temp[2] = c;
            Arrays.sort(temp);
            
            for (int x : temp) {
                if (x < 0)
                    continue;
                return x;
            }
        }
        return a < b ? (a < c ? a : c) : (b < c ? b : c);
    }
    
    //由於以前的那個,若是有數字溢出了,就會變成負數,負數確定最小而後就會被poll出來,最後結果就會有錯誤
    private class NewComparator implements Comparator<Integer> {

        @Override
        public int compare(Integer o1, Integer o2) {
            // TODO Auto-generated method stub
            if(o1 < 0)return 1;
            if(o1 < o2)return -1;
            else if(o1 == o2)return 0;
            else return 1;
        }
        
    }
    
}

 

 

 

但其實咱們能夠不用維護三個隊列:

連接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
來源:牛客網

們沒有必要維護三個隊列,只須要記錄三個指針顯示到達哪一步;「|」表示指針,arr表示醜數數組;
(1)1
|2
|3
|5
目前指針指向0,0,0,隊列頭arr[0] * 2 = 2,  arr[0] * 3 = 3,  arr[0] * 5 = 5
(2)1 2
2 |4
|3 6
|5 10
目前指針指向1,0,0,隊列頭arr[1] * 2 = 4,  arr[0] * 3 = 3, arr[0] * 5 = 5
(3)1 2 3
2| 4 6
3 |6 9
|5 10 15
目前指針指向1,1,0,隊列頭arr[1] * 2 = 4,  arr[1] * 3 = 6, arr[0] * 5 = 5
 
 
上一個簡化後不用維護三個隊列的Java的實現代碼:
public int GetUglyNumber_Solution2(int index) {
        if (index <= 0)
            return 0;
        ArrayList<Integer> list = new ArrayList<Integer>();
        // add進第一個醜數1
        list.add(1);
        // 三個下標用於記錄醜數的位置
        int i2 = 0, i3 = 0, i5 = 0;
        while (list.size() < index) {
            // 三個數都是可能的醜數,取最小的放進醜數數組裏面
            int n2 = list.get(i2) * 2;
            int n3 = list.get(i3) * 3;
            int n5 = list.get(i5) * 5;
            int min = Math.min(n2, Math.min(n3, n5));
            list.add(min);
            if (min == n2)
                i2++;
            if (min == n3)
                i3++;
            if (min == n5)
                i5++;
        }
        return list.get(list.size() - 1);
    }

 

能夠看見,就是三個指針在動。並且這裏彷佛不會出現溢出有負數的狀況,由於我那裏每次是用最新的醜數去乘2,3,5;而這裏是用以前的醜數,就i2,i3,i5指針所在的那個醜數來乘,因此好不少。

相關文章
相關標籤/搜索