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