題目:我們把只包含因子2,3和5的數稱作為醜數。求按從小到大的順序的第1500個醜數。例如6,8都是醜數,但是14不是,因為它包含因子7。習慣上我們把1作為第一個醜數。
方法一:遍曆法
使用遍曆法求第k個醜數,從1開始遍曆,如果是醜數則count++,直到count==k為止。那麼如何判斷醜數呢?根據醜數的定義,醜數只有2,3,5這三個因子,那麼我們就拿數字除以這三個因子。具體演算法如下:
- 如果一個數能夠被2整除,那麼讓他繼續除以2;
- 如果一個數能夠被3整除,那麼讓他繼續除以3;
- 如果一個數能夠被5整除,那麼讓他繼續除以5;
- 如果最後這個數變為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 []運算式釋放指標所指向的數組空間