若是一個整數值含有因數2,3,5(包括1和該整數自己)的整數稱爲醜數(Ugly Number)。換句話說醜數ugly_number是能夠表示成形以下面表達式的形式,表達式中的i,j,k均是大於等於0的整數。 git
舉個例子,18 = 2 * 3 * 3,因此18是一個醜數。而14 = 2 * 7,因此14不是一個醜數。 程序員
如今有個需求就是要找到第2013個醜數。 github
解決問題的思路顯然是要根據醜數的性質。主要有兩種思考的切入點。 面試
第一種方法,按照整數正好三順序一個一個找,並判斷該整數是否是醜數,若是是醜數的個數加1,若是不是繼續查找下一個,直到找到問題要求的第N個醜數便可,這種方法的效率不是很好,可是很好理解,因此在這裏只給出僞代碼。僞代碼以下: 算法
while(i < N) if(true == judge_ugly_number(number)) i++; ugly_numbers[i] = number; number++; else number++;判斷一個數是不是醜數的僞代碼以下:
while(0 == number % 2) number /= 2; while(0 == number % 3) number /= 3; while(0 == number % 5) number /= 5; if(1 == number) reutrn true; else return false;
第二種方法。第一種方法的思路是遍歷整數並判斷該整數是否是醜數。而第二種方法的思路則是根據醜數的性質直接計算醜數。 函數
爲實現方便咱們能夠另ugly_numbers[0] = 1。那麼來計算第一個醜數,根據醜數的性質,它只含有因數2,3,5因此咱們的醜數等於ugly_numbers[0]乘以這些因數。若是已經求得第i個醜數,那麼怎麼來求第i+1個醜數呢。咱們須要找到大於第i個醜數中最小的那個,那麼咱們須要在i前面的幾個醜數中分別乘以2,3,5找到那個大於第i個醜數的最小的那個。因此咱們還須要保留第i個前面的那幾個醜數的下標。從第一個醜數開始,若是該醜數是ugly_numbers * 2,則對2的index1進行加1,若是該醜數是ugly_numbers * 3,則對2的index2進行加1,若是該醜數是ugly_numbers * 5,則對5的index3進行加1。要好好分析這句話,根據計算順序,顯然一個醜數是2,第二個是3,第三個是4,第四個是5,第五個是6,而6能夠用醜數2乘以因數3獲得,也能夠用醜數3乘以因數2獲得。因此求出醜數六後須要對index1和index2都要進行加1操做。這個敘述的可能不太清楚,請讀者根據後面的程序實如今作進一步理解吧。(若是還不是很清楚的話能夠參考後面的參考資料2和3) 學習
第二種方法將給出具體的程序實現,(注意數據類型的選取,防止溢出)。 測試
#include <stdio.h> #define MAX_NUMBER 2048 long min(long a, long b, long c) { long temp = a < b ? a : b; return temp < c ? temp : c; } int main(int argc, char *argv[]) { long ugly_numbers[MAX_NUMBER]; ugly_numbers[0] = 1; int i = 1; int index1 = 0; int index2 = 0; int index3 = 0; int position; long min(long, long, long); for(; i < MAX_NUMBER; i++) { ugly_numbers[i] = min(2 * ugly_numbers[index1], 3 * ugly_numbers[index2], 5 * ugly_numbers[index3]); if(ugly_numbers[i] == 2 * ugly_numbers[index1]) index1++; if(ugly_numbers[i] == 3 * ugly_numbers[index2]) index2++; if(ugly_numbers[i] == 5 * ugly_numbers[index3]) index3++; } printf("Please input a integer number, we'll find the ugly number in that position(number < 2048)\n"); while(EOF != (scanf("%d", &position))) { printf("the ugly number is %ld\n", ugly_numbers[position]); printf("Please input a integer number, we'll find the ugly number in that position(number < 2048)\n"); } return 0; }
下面咱們來計算一下程序中的for循環運行的時間,須要用到time.h,這個的用法還在總結中,總結好會在會C/C++學習中給出來。 ui
下面先給出計算時間函數: google
void count_time(struct timespec start, struct timespec end) { struct timespec countTime; long duration; const long NANOSECOND = 1000000000l; if(0 > (end.tv_nsec - start.tv_nsec)) { countTime.tv_sec = end.tv_sec - start.tv_sec - 1; countTime.tv_nsec = NANOSECOND + end.tv_nsec - start.tv_nsec; } else { countTime.tv_sec = end.tv_sec - start.tv_sec; countTime.tv_nsec = end.tv_nsec - start.tv_nsec; } duration = NANOSECOND * (int)countTime.tv_sec + countTime.tv_nsec; printf("compute the ugly numbers took %ld nsec \n",duration); }咱們回到程序中去測試那個for循環的耗時:
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpStart); for(; i < MAX_NUMBER; i++) { ugly_numbers[i] = min(2 * ugly_numbers[index1], 3 * ugly_numbers[index2], 5 * ugly_numbers[index3]); if(ugly_numbers[i] == 2 * ugly_numbers[index1]) index1++; if(ugly_numbers[i] == 3 * ugly_numbers[index2]) index2++; if(ugly_numbers[i] == 5 * ugly_numbers[index3]) index3++; } clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpEnd); count_time(tpStart, tpEnd);
這裏測試5次,能夠得出這5次的耗時(單位是納秒):56859,57432,55523,56880,57316,能夠計算得出平均耗時爲56802。
上述的測試程序中每次都在調用min函數,咱們如今在這個for循環內部實現這個功能並測試其耗時:
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpStart); for(; i < MAX_NUMBER; i++) { temp = (2 * ugly_numbers[index1]) < (3 * ugly_numbers[index2]) ? (2 * ugly_numbers[index1]) : (3 * ugly_numbers[index2]); temp = temp < (5 * ugly_numbers[index3]) ? temp : (5 * ugly_numbers[index3]); if(ugly_numbers[i] == 2 * ugly_numbers[index1]) index1++; if(ugly_numbers[i] == 3 * ugly_numbers[index2]) index2++; if(ugly_numbers[i] == 5 * ugly_numbers[index3]) index3++; } clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tpEnd); count_time(tpStart, tpEnd);
依然測試5次, 能夠得出這5次的耗時(單位是納秒):25767,26283,25473,25607,27326,能夠計算得出平均耗時爲26091。
結果很明顯,第二個測試比第一個測試要省多一半的時間。節省的時間就是每次for循環都要調用min函數的時間。並且要是考慮空間開銷的話,測試的第二種方法會比較省空間,因此在for循環中最好不要頻繁的調用一個實現簡單功能的函數。可是不要覺得這個違反了時間複雜度和空間複雜度是一對矛盾體,由於第一種測試中會調用一個額外的函數,因此比較浪費空間,並且每次都調用也比較費時。
總的來講,要想提高時間效率就會須要拿空間來換,要想節約空間就要拿時間來換。
1. More Programming Perls Confessions of a Coder, Jon Bentley
2. google面試題目 尋找醜數--使用double防止數據溢出:
http://blog.csdn.net/shihui512/article/details/8833568
3. 程序員面試題精選100題(37)-尋找醜數[算法]:
http://zhedahht.blog.163.com/blog/static/2541117420094245366965/
說明:
若有錯誤還請各位指正,歡迎你們一塊兒討論給出指導。
上述程序完整代碼的下載連接:
https://github.com/zeliliu/BlogPrograms/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%26%E7%AE%97%E6%B3%95/ugly%20numbers
最後更新時間:2013-05-05