[刷題] 劍指offer之醜數

題目描述

題目:把只包含質因子二、3和5的數稱做醜數(Ugly Number)。例如六、8都是醜數,但14不是,由於它包含質因子7。 習慣上咱們把1當作是第一個醜數。求按從小到大的順序的第N個醜數。c++

輸入輸出

  • 1 --> 1
  • 10 --> 12
  • 400 --> 311040
  • 1500 --> 859963392

方法一

思路

動態規劃方法,假設這個數爲 n, 若是n是醜數,只有三種可能:算法

  1. n是能整除2,即 n % 2 == 0,且 n/2 是醜數。
  2. n % 3 == 0n/3是醜數。
  3. n % 5 == 0n / 5是醜數。

三種可能只要知足其中一種,就能夠確認是醜數了。測試

代碼

使用dp[i]表示i是否爲醜數。因爲不知道第index個醜數究竟是第幾個數,因此使用vector保存。優化

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index <= 6)  // 1...6都是醜數
            return index;
        vector<bool> dp(7,true);

        int count = 6;
        int i = 7; //從數字7開始判斷
        while(1){
            dp.push_back(false); // 默認第i個不是醜數
            if(i%2 == 0 && dp[i/2]) {
                dp[i] = true;
                count++;
            }
            else if(i%3 == 0 && dp[i/3]) {
                dp[i] = true;
                count++;
            }
            else if(i%5 == 0 && dp[i/5]) {
                dp[i] = true;
                count ++;
            }
            if(count == index)
                break;
            i++; // 判斷下一個數是否是醜數
        }
        return i;
    }
};

結果

用小數據測試了一下,看起來挺正確的,可是提交發現,過不了,緣由是內存超限:您的程序使用了超過限制的內存翻譯

反思一下,當求第1500個醜數的時候,確實內存太大了。因而想辦法優化了下內存。c++11

方法二

思路

爲了優化內存,只能想辦法不存儲全部的數了,而是用一個集合存儲全部的醜數,若是要求第n個醜數,也就最多使用n個存儲空間。而判斷一個數是否是醜數的方法就是判斷該數在不在這個集合中。code

爲了查找的快速,選擇使用c++11unordered_set容器。索引

代碼

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if( index == 1)
            return 1;
        unordered_set <int> mp;
        mp.insert(1); // 1是醜數
        int count = 1;
        int i = 2;
        while(1){
            // 能整除2, 且 i/2在醜數集合中
            if(i%2 == 0 && mp.find(i/2)!=mp.end()) {
                mp.insert(i); //是醜數。添加到集合中
                count++;
            }
            else if(i%3==0 && mp.find(i/3)!=mp.end()) {
                mp.insert(i);
                count++;
            }
            else if(i%5==0 && mp.find(i/5)!=mp.end()) {
                mp.insert(i);
                count ++;
            }
            if(count == index)
                break;
            i++;
        }
        return i;
    }
};

結果

內存確實優化了,卻仍是沒過!!! 此次是 運行超時:您的程序未能在規定時間內運行結束,請檢查是否循環有錯或算法複雜度過大。想了半天也沒能再優化下去,只得看看參考答案了。內存

方法三--最終方案

思路

其實仍是一樣的思路,不過換了個角度。若是已知了n個醜數,第n+1個醜數必然是前面的某個醜數乘以2,或者乘以3,或者乘以5。至因而誰,就是都嘗試一下,取最小。get

舉個栗子:

如今已知6個醜數 1 2 3 4 5 6, 求第7個醜數。

能夠翻譯成:假設dp[i]表示第i個醜數的數值,已知醜數的個數爲count=6,且前6個醜數 dp[1]=1;dp[2]=2;dp[3]=3;dp[4]=4;dp[5]=5;dp[6]=6;dp[7]

dp[7]可能有三種狀況:

  • i=1開始按順序求v = dp[i]*2,當v>dp[6],能夠中止,則第4個醜數乘2獲得的8多是第7個醜數。
  • i=1開始按順序求v = dp[i]*3,當v>dp[6],能夠中止,則第3個醜數乘3獲得的9多是第7個醜數。
  • i=1開始按順序求v = dp[i]*5,當v>dp[6],能夠中止,則第3個醜數乘5獲得的10多是第7個醜數。

取三種狀況的最小值,獲得8,就是第7個醜數,即dp[7] = 8

依此類推,能夠求得第8個醜數。

注意,這裏有個小優化,按順序搜索的時候並不須要每次都從1開始,只須要從上次搜索的結束點繼續搜索就好了。

例如求dp[8],一樣有三種狀況:

  • i=4開始按順序求v = dp[i]*2,當v>dp[7],能夠中止,則第5個醜數乘2獲得的10多是第8個醜數。
  • i=3開始按順序求v = dp[i]*3,當v>dp[7],能夠中止,則第3個醜數乘3獲得的9多是第8個醜數。
  • i=2開始按順序求v = dp[i]*5,當v>dp[7],能夠中止,則第2個醜數乘5獲得的10多是第8個醜數。

取三種狀況的最小值,獲得10,即dp[8] = 9

代碼

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index <= 6)
            return index;

        int start1=1, start2=1, start3 = 1; //搜索起點
        vector<int> dp(index+1, 0);
        for(int i=1;i<=6;i++)
            dp[i] = i;
        int count = 6; //當前醜數的最大索引
        while(count < index){

            //使用*2第一次超過已知的最大丑數 dp[count] 的
            for(int i=start1;i<=count;i++){
                if(2*dp[i] > dp[count]){
                    start1 = i; //記錄下這個索引,下次從這裏開始
                    break; // 找到第一個大於就中止搜索
                }
            }

            for(int i=start2;i<=count;i++){
                if(3*dp[i] > dp[count]){
                    start2 = i;
                    break;
                }
            }

            for(int i=start3;i<=count;i++){
                if(5*dp[i] >  dp[count]){
                    start3 = i;
                    break;
                }
            }

            count++;
            dp[count] = min(dp[start1]*2, dp[start2]*3);
            dp[count] = min(dp[count], dp[start3]*5);

        }

        return dp[index];
    }
};

總結

本身想出來的方法比較複雜,第1500個醜數是859963392,那麼,個人方法就得判斷859963392前面的全部數究竟是不是醜數,時間複雜度很高。

而正確解法,只用遍歷前面的1499個醜數,就能夠計算出第1500個醜數的值!

相關文章
相關標籤/搜索