264.丑数II

[TOC]

一、题目描述

编写一个程序,找出第 n 个丑数。

丑数就是只包含质因数 2, 3, 5 的正整数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

说明:

1 是丑数。
n 不超过1690。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ugly-number-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、解题方法

1. 暴力法


思路:

最原始的暴力法,令u_i表示第i个丑数的实际数值,则对于从1 \sim u_n的每一个数字,我们都令它对235做因式分解,来判定他是否为丑数,直至找到第n个丑数。
粗略估算,一个计算的上界是\sum_{i=1}^{u_n}log\ i
进一步的简化估算,我们可以得到时间复杂度:O(u_nlog\ u_n),空间复杂度:O(1)

2. 记忆化搜索


思路:

我们重新思考一下题目,丑数的定义是“只包含质因数 2, 3, 5 的正整数”。
那反过来说,"若k为大于1的丑数,则k/2k/3k/5中至少有一个是丑数"。
利用这这一点,我们可以得到一个递推方程:
dp[i] = \begin{cases} 1, & i\text{ mod }2=0\quad \text{ and }\quad dp[i/2]=1 \\ 1, & i\text{ mod }3=0\quad \text{ and }\quad dp[i/3]=1 \\ 1, & i\text{ mod }5=0\quad \text{ and }\quad dp[i/5]=1 \\ 0, & others \end{cases}
初始条件有dp[1]=1,令u_i表示第i个丑数的实际数值,则对于2 \sim u_n,我们都可以在常数时间内判断出它是否为丑数,但我们需要保存1 \sim u_n中的每一个数字,以方便之后的快速判断。

时间复杂度:O(u_n),空间复杂度:O(u_n)

3. 递推


思路:

上述两个方法,都需要计算u_n个数字,考虑到丑数虽然前期分布比较密集,但到后期相对就会稀疏很多,因而这样的计算量有些浪费。

在方法2中,我们提到,“若k为大于1的丑数,则k/2k/3k/5中至少有一个是丑数”,并且给出了递推方程。但观察递推方程,我们可以明显地看到,需要进行的判断太多了,不够优雅。
这句话如果反过来说就是,“若k为丑数,则2k3k5k都是丑数”。

可以利用这一点进行迅速递推,对于每一个丑数,我们都可以得到三个新的丑数,那么对于第n个丑数,必定会在至多3n次乘法计算中出现(某些丑数会被计算多次,比如6可以分别由2和3递推得到)

嗯?等等,存在重复?存在重复不利于我们计算第n个吧?因为谁知道前面出现了多少重复?必须得把重复去掉。
那么,怎么去重复呢?常见的手段,自然就是使用哈希表来做标记了。

至此,我们已经完成了查重的判断了。下一个步骤就是在一堆没有重复的丑数当中,取出第n大的丑数。

那么,该怎么做呢?

由于我们是要求第n个丑数,换句话说就是要求这些丑数中第n小的。这样我们不由自主地会想到对数组排序,或者直接使用堆。
进一步的,显然我们希望每一次都拿出还未进行递推的丑数中最小的那一个进行递推(这样能避免真的每次都递推到3n)
结合二者,于是这里决定选择堆来处理。进一步的,因为每次都要取出最小的,我们决定使用小顶堆来处理。

对于每一个丑数,递推出三个新的丑数复杂度为O(1),哈希表中查重平均复杂度为O(1),取堆顶部元素复杂度为O(1),往堆中插入和删除元素复杂度都为O(logn),而外循环共需执行n次。

于是我们有,总的时间复杂度:O(n+nlogn),总的空间复杂度:O(n)


代码:

class Solution {
public:
    int nthUglyNumber(int n) {
        if (n < 7)      return n;

        priority_queue<unsigned long long, vector<unsigned long long>, greater<unsigned long long>> q;
        unordered_set<unsigned long long> s;
        q.push(1);
        s.insert(1);
        while (--n) {
            auto num = q.top();
            q.pop();
            if (s.find(num * 2) == s.end()) {
                q.push(num * 2);
                s.insert(num * 2);
            }
            if (s.find(num * 3) == s.end()) {
                q.push(num * 3);
                s.insert(num * 3);
            }
            if (s.find(num * 5) == s.end()) {
                q.push(num * 5);
                s.insert(num * 5);
            }
        }
        return q.top();
    }
};

4. 动态规划


思路:

方法3是已经足够解决问题了,但总觉得好像还不够看。
为了判断重复而单独使用了一个哈希表,对于强迫真来说这真的令人坐立不安。有没有什么办法能取消掉那个哈希表?

既然想要去掉哈希表,那么首先肯定得搞清楚这里的哈希表有什么用。正如方法3所属,因为递推的时候很可能递推到重复的数字,所以我们使用哈希表进行去重。
那么想去掉哈希表,问题就转化成了,如何避免递推到重复的数字。

要避免递推到重复的数字,我们可以将计算目标转换为当前这个丑数,而不是递推出的另外三个丑数。记当前正在计算的第i个丑数为dp[i]。根据丑数的定义,我们知道,1以外的丑数,或者是某个丑数乘以2,或者是某个丑数乘以3,或者是某个丑数乘以5。如果分别设这三个数字为xyz,我们一定dp[i] = min(2x, 3y, 5z)(因为升序,所以每个丑数因该是能得到的最小值)

于是进一步的,问题又转化为如何获得并维护xyz。由于起初的三个丑数235可以看作是2 \times 13 \times 15 \times 1,所以最初xyz都可以设置为1。那么接着当计算出一个新的丑数的时候,该如何对xyz进行维护呢?可以想象一下,以x为例,当dp[i] \ge 2x时,显然x已经不足以用去递推dp[i+1],因而我们应该将x设置为比x大的丑数。同时为了保证dp[i+1]尽可能地小,x应该仅仅向后移动一位,更新为仅次于x的丑数yz同理

时间复杂度:O(n),空间复杂度:O(n)


代码:

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

推荐阅读更多精彩内容

  • 题目描述 编写一个程序,找出第 n 个丑数。 丑数就是只包含质因数 2, 3, 5 的正整数。 示例 1: 输入:...
    zhipingChen阅读 223评论 0 1
  • 题目链接难度:中等 类型: 数学、数组 编写一个程序,找出第 n 个丑数。 丑数就是只包含质因数...
    wzNote阅读 491评论 0 1
  • 题目描述 编写一个程序,找出第 n 个丑数。 丑数就是只包含质因数 2, 3, 5 的正整数。 示例: 思路 1....
    youzhihua阅读 222评论 0 0
  • 编写一个程序,找出第 n 个丑数。 丑数就是只包含质因数 2, 3, 5 的正整数。 示例: 输入: n = 10...
    calm_peng阅读 283评论 0 0
  • “都知道的,我谈过很多次恋爱,次数多了,不免相信量变生成质变,觉得上了无数趟秋名山,即便出车祸也不至于把引擎撞烂。...
    LiebeNiall阅读 88评论 0 0