[刷题] 剑指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个丑数的值!

相关文章
相关标签/搜索