1. 程式人生 > 實用技巧 >劍指offer——醜數

劍指offer——醜數

我們把只包含質因子 235 的數稱作醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。



示例:

輸入: n = 10
輸出: 12
解釋: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 個醜數。
說明:

1是醜數。
n不超過1690。

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/chou-shu-lcof

題目對醜數的概念很簡短,可能一時間看不明白。

因子:現在有n*m=p,則n和m是p因子。比如2*6=12,那麼2和6就是12的因子。
質數:一個數的因子只有1和這個數本身,那麼這個數就是質數。比如2的因子只有1和2;5的因子只有1和5;所以2,5是質數。

醜數:有質因子2,3,5,醜數=小丑數*(2或3或5),醜數的因子分解出來可以全是2,3,5。比如12=2*2*3,10=2*5,9=3*3*3。公式為:2x

*3y*5z

解題思路:
由上面的說法可以想出一種簡單方法來判斷一個數number是否是醜數:
將number迴圈除以2,直到不能整除。
再將得到的商分別迴圈除以3,5,如果最後能夠整除,且商為1,那麼這個數是醜數。

例如:
12/2=6 6/2=3 3/2=1...1(有餘數1,換3) 3/3=1 (符合條件,為醜數)
14/2=7 7/2=3...1 (有餘數,換3) 7/3=2...1 (有餘數,換5) 7/5=1...2 (不符合條件,不是醜數)

判斷醜數程式碼如下:

var isUgly=function(num){

        while(num%2==0)
        num=num/2;

        while
(num%3==0) num=num/3; while(num%5==0) num=num/5; return ( num===1)?true:false; }

要找出第n個醜數,那麼可以選擇遍歷

解1

var isUgly=function(num){

        while(num%2==0)
        num=num/2;

        while(num%3==0)
        num=num/3;

        while(num%5==0)
        num=num/5;

    return ( num===1)?true
:false; } var nthUglyNumber = function(n) { if(n<=0) return 0; var index=0; var current=0; while(index<n){ current++; if(isUgly(current)){ index++; } } return current; };

這樣的方法理解起來比較容易,但是明顯的缺點就是時間消耗比較大,每個數都要判斷是不是醜數,然後我們要找第1600個醜數呢?那麼我們將會判斷上億個數字是否為醜數。

下面有一種是靠空間換時間的方法,是劍指offer書上提供的方法。具體解析參考書上的解釋。

解題思路:

如果我們將已經找到的醜數按從小到大的方式有序的存入陣列uglyArr。現在知道的最大丑數為M,我們需要找到下一個醜數。下一個醜數MNEXT應該是當前uglyArr陣列中的某個醜數的2倍,3倍或者5倍。我們可以將uglyArr陣列中的數都變成2倍,3倍或5倍。他們都是醜數,但是我們要找的Mnext是大於M的醜數中最小的那一個。比如現在uglyArr=[1,2,3,4,5],M=5,我們現在找Mnext。

2倍:uglyArr*2=[2,4,6,8,10]

3倍:uglyArr*3=[3,6,9,12,15]

5倍:uglyArr=[5,10,15,20,25]

比5大的數:6,8,10,12,15,20,25。Mnext是大於M的醜數中最小的那一個Mnext=6。這樣就找到了下一個醜數。

我們還可以繼續優化,因為uglyArr陣列中有些數是可以在2,3,5倍時只比M大一點點,我們只需要找出這個臨界的數即可,設為t2,t3,t5。就如上面例子中,t2=3,t3=2,t5=2,t2*2=6, t3*3=6,t5*5=10。6,6,10相比,最小為6。

解2

var minNum=function(a,b,c){
    return a<b?(a<c?a:c):(b<c?b:c)
}
var nthUglyNumber = function(n) {
    //如果小於等於0,那麼可以直接返回0
    if(n<=0)
    return 0;
    
    var uglyArr=[1];//初始化醜數陣列,這將是一個有序的醜數陣列
    var currentIndex=1;//當前要尋找的醜數在陣列中的索引位置
    
    //t2,t3,t5代表*2,*3,*5剛好大於或等於當前醜數陣列中最大的醜數,最開始初始化為1
    var t2=1;
    var t3=1;
    var t5=1;
    

    while(currentIndex<n){
        var min=minNum(t2*2,t3*3,t5*5);//下一個醜數是t2*2,t3*3,t5*5中最小的
        uglyArr.push(min);

        //尋找臨界值
        var temp=0;
        while(uglyArr[temp]*2<=min)
        temp++;

        t2=uglyArr[temp];

        temp=0;
        while(uglyArr[temp]*3<=min)
        temp++;

        t3=uglyArr[temp];

        temp=0;
        while(uglyArr[temp]*5<=min)
        temp++;

        t5=uglyArr[temp];

        currentIndex++;


    }

    return uglyArr[currentIndex-1];

};

也可以用動態規劃的方法來實現,參考https://leetcode-cn.com/problems/chou-shu-lcof/solution/mian-shi-ti-49-chou-shu-dong-tai-gui-hua-qing-xi-t/,這裡寫的解釋很詳細,圖文並茂,我這裡用javascript來實現

解3

var minNum=function(a,b,c){
    return a<b?(a<c?a:c):(b<c?b:c)
}
var nthUglyNumber = function(n) {
    if(n<=0)
    return 0;
    
   var a=0,
   b=0,
   c=0;//三個位置

   var dp=[1]

   for(var i=0;i<n;i++){
        var min=minNum(dp[a]*2,dp[b]*3,dp[c]*5);
        dp.push(min)
        while(dp[a]*2<=min)
            a++;
        while(dp[b]*3<=min)
            b++;
        while(dp[c]*5<=min)
            c++;
   }

   return dp[n-1];

};