面試題34:丑數


題目:我們把只包含因子2,3和5的數稱作為丑數。求按從小到大的順序的第1500個丑數。例如6,8都是丑數,但是14不是,因為它包含因子7。習慣上我們把1作為第一個丑數。

方法一:遍歷法

使用遍歷法求第k個丑數,從1開始遍歷,如果是丑數則count++,直到count==k為止。那么如何判斷丑數呢?根據丑數的定義,丑數只有2,3,5這三個因子,那么我們就拿數字除以這三個因子。具體算法如下:

  1. 如果一個數能夠被2整除,那么讓他繼續除以2;
  2. 如果一個數能夠被3整除,那么讓他繼續除以3;
  3. 如果一個數能夠被5整除,那么讓他繼續除以5;
  4. 如果最后這個數變為1,那么這個數就是丑數,否則不是。

代碼實現如下:

View Code
#include<iostream>
#include<stdlib.h>
#include<cassert>
#include<time.h>
using namespace std;

//判斷是否為丑數
bool isUgly(int number)
{
    while(number%2==0)
        number=number/2;
    while(number%3==0)
        number=number/3;
    while(number%5==0)
        number=number/5;
    return (number==1)?true:false;
}

//獲取第k個丑數,假定1為第一個丑數
int getUglyNumber(int index)
{
    int number=0;
    int count=0;
    while(count<index)
    {
        ++number;
        if(isUgly(number))
            count++;
    }
    return number;
}

int main()
{
    int k=1500;
    clock_t start, end;//用於統計程序運行時間
    start = clock();
    cout<<getUglyNumber(k)<<endl;
    end = clock();
    cout<<"Run time: "<<(double)(end - start) / CLOCKS_PER_SEC<<"S"<<endl;//要記錄分鍾的話,上面的程序改為(double)(end - start) / CLOCKS_PER_SEC/60即可;
    system("pause");
    return 0;
}

其運行結果如下:

我們發現一共耗時33秒,性能比較低。

方法二:創建丑數數組,用空間還時間

如前所述,我們發現采用遍歷法求第K個丑數的效率十分低下,我們在前面求第1500個丑數花去了33秒的時間,這還是在我I7 3770K的電腦上運行的。所以我們考慮有沒有一種更加高效的方法。在面試題9:斐波那契數列中我們使用了一種“用空間還時間”的方法來提高求斐波那契數列的速度。這種編程思想也可以應用在這道題目當中,我們為所有求出的丑數創建數組,不在非丑數上面浪費時間。

根據丑數的定義,我們可以知道丑數可以由另外一個丑數乘以2,3或者5得到。因此我們創建一個數組,里面的數字是排好序的丑數,每一個丑數都是前面的丑數乘以2,3或者5得到的。這種思路的關鍵在於怎樣確保數組里面的數字是排序的。

假設丑數數組中已經有若干個排好序的丑數,比如1,2,3,4,5。我們把當前丑數數組中的最大數記為M,這里M=5。我們接下來分析如何生成下一個丑數。根據前面的介紹,我們知道這個丑數肯定是前面丑數數組中的數字乘以2,3,5得到的。所以我們首先考慮把已有的每個丑數乘以2,在乘以2的時候,能夠得到若干個小於或者等於M的結果。由於是按照順序生成的,小於或者等於M的數肯定已經在丑數數組當中了,我們不需要再次考慮;當然還會得到若干大於M的結果,但是我們只需要第一個大於M的結果,因為我們希望丑數是按順序排列的,所以其他更大的結果可以以后考慮。我們把得到的第一個乘以2以后得到的大於M的結果記為M2。同樣,我們把已有的每一個丑數乘以3和5,能得到第一個大於M的結果M3和M5。那么M后面的那一個丑數應該是M2,M3和M5當中的最小值:Min(M2,M3,M5)。比如將丑數數組中的數字按從小到大乘以2,直到得到第一個大於M的數為止,那么應該是2*2=4<M,3*2=6>M,所以M2=6。同理,M3=6,M5=10。所以下一個丑數應該是6。

前面分析的時候,提到把已有的每個丑數分別都乘以2,3和5。事實上這不是必須的,因為已有的丑數是按順序存放在數組中的,對乘以2而言,肯定存在某一個丑數T2,排在她之前的每一個丑數乘以2得到的結果都會小於等於(<=)已有最大的丑數,在它之后的每一個丑數乘以2得到的結果都會大於已有的最大丑數。因此我們只需要記下這個丑數的位置,同時每次生成新的丑數的時候去更新這個T2。對於乘以3和5,同樣存在這樣的T3和T5。

代碼示例

View Code
#include<iostream>
#include<stdlib.h>
#include<cassert>
#include<time.h>
using namespace std;

//求M2,M3,M5的最小值
int Min(int number1,int number2,int number3)
{
    int min=(number1<number2)?number1:number2;
    return (min<number3)?min:number3;
}

//獲取第k個丑數,假定1為第一個丑數
int getUglyNumber2(int index)
{
    //如果index<=0表明輸入有誤,直接返回0
    if(index<=0)
        return 0;

    //定義丑數數組,用於記錄排序的丑數
    int *pUglyNumbers=new int[index];
    //第一個丑數為1
    pUglyNumbers[0]=1;
    //第一個丑數的坐標是0,下一個丑數的坐標從1開始
    int nextUglyIndex=1;
    //定義三個指向丑數數組的指針,用它們來標識從數組中的哪一個數開始計算M2,M3和M5,開始都是丑數數組的首地址。
    int *T2=pUglyNumbers;
    int *T3=pUglyNumbers;
    int *T5=pUglyNumbers;

    while(nextUglyIndex<index)//
    {
        int min=Min(*T2 * 2,*T3 * 3,*T5 * 5);//M2=*T2 * 2, M3=*T3 * 3, M5=*T5 * 5
        pUglyNumbers[nextUglyIndex]=min;//求M2,M3,M5的最小值作為新的丑數放入丑數數組
        //每次生成新的丑數的時候,去更新T2,T3和T5.
        while(*T2 * 2<=pUglyNumbers[nextUglyIndex])
            ++T2;
        while(*T3 * 3<=pUglyNumbers[nextUglyIndex])
            ++T3;
        while(*T5 * 5<=pUglyNumbers[nextUglyIndex])
            ++T5;
        nextUglyIndex++;
    }
    int ugly=pUglyNumbers[index-1];//因為丑數有序排列,所以丑數數組中的最后一個丑數就是我們所求的第index個丑數。
    delete[] pUglyNumbers;
    return ugly;
}

int main()
{
    int k=1500;
    clock_t start, end;//用於統計程序運行時間
    start = clock();
    cout<<getUglyNumber2(k)<<endl;
    end = clock();
    cout<<"Run time: "<<(double)(end - start) / CLOCKS_PER_SEC<<"S"<<endl;//要記錄分鍾的話,上面的程序改為(double)(end - start) / CLOCKS_PER_SEC/60即可;

    system("pause");
    return 0;
}

注意點:在程序最后有delete[] pUglyNumbers;這是因為動態數組與數組變量不同,動態分配的數組將一直存在,直到程序顯式釋放它為止。普通的數組變量,只要出了數組的作用於,其內存會自動釋放。c++提供delete []表達式釋放指針所指向的數組空間

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM