[刷题] 剑指offer之丑数

题目描述

题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

输入输出

  • 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个丑数的时候,确实内存太大了。于是想办法优化了下内存。

方法二

思路

为了优化内存,只能想办法不存储所有的数了,而是用一个集合存储所有的丑数,如果要求第n个丑数,也就最多使用n个存储空间。而判断一个数是不是丑数的方法就是判断该数在不在这个集合中。

为了查找的快速,选择使用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。至于是谁,就是都尝试一下,取最小。

举个栗子:

现在已知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个丑数的值!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,320评论 0 2
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,719评论 0 33
  • 我都不想写这几个字了。 我成天喊口号,喊到无力,却没有切实的舍去什么东西。 今天看丽萍想把曾经积压的库存的新衣服都...
    lindacui阅读 184评论 0 0
  • 从十八大到十九大五年时间过去了,而我从2012年毕业到现在也已经五年了,这五年吃了一些苦,受了一些委屈,也获得了很...
    StoneSir阅读 86评论 0 0
  • 通过对重要功能、信息进行分类和组织,可以帮助用户迅速找到入口,进入一个具体的界面,再通过界面上的信息提示完成各种任...
    苏千千0046阅读 892评论 0 0