醜數

題目:我們把只包含因子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,那麼這個數就是醜數,否則不是。

代碼實現如下:

複製代碼
#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。

代碼示例

複製代碼
#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 []表達式釋放指針所指向的數組空間

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