[剑指-Offer] 49. 丑数(思维、代码优化、巧妙解法)

1. 题目来源

链接:丑数
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

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

说明:

  • 1 是丑数。
  • n 不超过1690。

3. 题目解析

方法一:TLE+常规解法

根据丑数的定义,它只能被 2、3、5 整除,那么这个数能被 2、3、5 其中一个整除时,就连续让它除以 2 / 3 / 5,若最后得到的结果是 1,说明它是丑数,否则说明它不是丑数。然后顺序计算得到 n 个丑数并输出即可。

虽然代码很简洁、直观,但是频繁的求余、除法运算是非常耗时的,最终程序不能 ACTLE 了,当 n = 1352 时就无法通过了。

参见代码如下:

// TLE
// 500 / 596 个通过测试用例
// 最后执行的输入:1352

class Solution {
public:
    int nthUglyNumber(int n) {
        long long cnt = 0, tmp = 0;
        while (cnt < n) {
            ++tmp;
            if (judge(tmp)) {
                ++cnt;
            }
            
        }
        return tmp;
    }

    bool judge(int n) {
        while (n % 2 == 0) n /= 2;
        while (n % 3 == 0) n /= 3;
        while (n % 5 == 0) n /= 5;

        if (n == 1) return true;

        return false;
    }
};

方法二:思维+巧妙解法

根据《剑指-Offer》讲解内容,丑数序列可以拆分为下面3个子列表:

  • 1x2, 2x2, 2x2, 3x2, 3x2, 4x2, 5x2…
  • 1x3, 1x3 ,2x3, 2x3, 2x3, 3x3, 3x3
  • 1x5, 1x5, 1x5, 1x5, 2x5, 2x5, 2x5…

仔细观察上述三个列表,可以发现每个子列表都是一个丑数分别乘以 2,3,5,而要求的丑数就是从已经生成的序列中取出来的,每次都从三个列表中取出当前最小的那个加入序列即可。

参见代码如下:

// 执行用时 :16 ms, 在所有 C++ 提交中击败了33.51%的用户
// 内存消耗 :12 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> res(1, 1);
        int i2 = 0, i3 = 0, i5 = 0;
        while (res.size() < n) {
            int m2 = res[i2] * 2, m3 = res[i3] * 3, m5 = res[i5] * 5;
            int mn = min(m2, min(m3, m5));
            if (mn == m2) ++i2;
            if (mn == m3) ++i3;
            if (mn == m5) ++i5;
            res.push_back(mn);
        }
        return res.back();
    }
};

方法三:思维+堆+巧妙解法

思想同方法二,采用空间换时间的做法,可以使用最小堆来做,具体思路如下:

  • 首先放进去一个 1
  • 然后从 1 遍历到 n,每次取出堆顶元素,为了确保没有重复数字,进行一次 while 循环,将此时和堆顶元素相同的都取出来,然后分别将这个取出的数字乘以 2,3,5,并分别加入最小堆。这样最终 for 循环退出后,堆顶元素就是所求的第 n 个丑数。

参见代码如下:

// 执行用时 :236 ms, 在所有 C++ 提交中击败了5.11%的用户
// 内存消耗 :13.1 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue<long, vector<long>, greater<long>> pq;
        pq.push(1);
        for (long i = 1; i < n; ++i) {
            long t = pq.top(); pq.pop();
            while (!pq.empty() && pq.top() == t) {
                t = pq.top(); pq.pop();
            }
            pq.push(t * 2);
            pq.push(t * 3);
            pq.push(t * 5);
        }
        return pq.top();
    }
};
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值