剑指Offer49—丑数

题意

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

丑数即只能被 2、3、5 整除。判断一个数是不是丑数的代码如下:

bool is_uglyNumber(int num)
{while(num%2==0)num=num/2;while(num%3==0)num=num/3;while(num%5==0)num=num/5;return num==1;
}

法1—最小堆

要得到从小到大的第 n 个丑数,可以使用最小堆实现。初始时堆为空。首先将最小的丑数 1 加入堆。每次取出堆顶元素 x,则 x 是堆中最小的丑数,由于 2x, 3x, 5x 也是丑数,因此将 2x, 3x, 5x加入堆。

上述做法会导致堆中出现重复元素的情况。为了避免重复元素,可以使用哈希集合(unordered_set)去重,避免相同元素多次加入堆。

在排除重复元素的情况下,第 n 次从最小堆中取出的元素即为第 n 个丑数。

C++实现

class Solution
{
public:int nthUglyNumber(int n) {if(n<=1)return n;//用来与当前取出的丑数相乘,得到之后的丑数vector<int> factors{2,3,5}; //最小堆,用来存放丑数,由于丑数范围可能超出int,所以用longpriority_queue<long,vector<long>,greater<long>> min_heap;//使用集合去重unordered_set<long> set;//第n个丑数int ugly_number;//首先将最小的丑数加入最小堆和集合中set.insert(1L); //1L表示long类型的1min_heap.push(1L);for(int i=0;i<n;++i){long temp = min_heap.top();min_heap.pop();ugly_number=static_cast<int>(temp);for(auto& num:factors){long next_guly_number=num*temp;//如果哈希集合里面没有重复的丑数if(set.find(next_guly_number)==set.end()){min_heap.push(next_guly_number);set.insert(next_guly_number);}}}return ugly_number;}
};

复杂度分析 :

  • 时间复杂度:O(nlogn)。得到第 n 个丑数需要进行 n 次循环,每次循环都要从最小堆中取出 1 个元素以及向最小堆中加入最多 3 个元素,因此每次循环的时间复杂度是 O(logn+log3n)=O(logn),总时间复杂度是O(nlogn)。
  • 空间复杂度:O(n)。空间复杂度主要取决于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不会超过 3n

法2—动态规划

方法一使用最小堆,会预先存储较多的丑数,导致空间复杂度较高,维护最小堆的过程也导致时间复杂度较高。可以使用动态规划的方法进行优化。

定义数组 dp,其中 dp[i] 表示第 i 个丑数,第 n 个丑数即为 dp[n]。

由于最小的丑数是 1,因此dp[1]=1。如何得到其余的丑数呢?我们知道,丑数只包含因子 2, 3, 5,因此有 “丑数 == 某较小丑数 × 某因子”(例如:10=5*2,较大丑数10等于较小丑数5*2这个因子)。

定义三个指针 p2​,p3​,p5​,表示下一个丑数是当前指针(p2、p3、p5)指向的丑数乘以对应的质因数 中 最小的一个。

初始时,三个指针的值都是 1。当 2 ≤ i ≤n 时,令:dp[i] = min( dp[p2]*2,dp[p3]*3,dp[p5]*5 ),然后分别比较 dp[i]和 dp[p2]*2、dp[p3]*3、dp[p5]*5相不相等,如果相等则将对应的指针加 1。


三指针解释:

例如 n = 10, primes = [2, 3, 5]。 打印出丑数列表:1, 2, 3, 4, 5, 6, 8, 9, 10, 12

首先一定要知道,后面的丑数一定由前面的丑数乘以2,或者乘以3,或者乘以5得来。例如,8,9,10,12一定是1, 2, 3, 4, 5, 6乘以2,3,5三个质数中的某一个得到。

这样的话我们的解题思路就是:从第一个丑数开始,一个个数丑数,并确保数出来的丑数是递增的,直到数到第n个丑数,得到答案。那么问题就是如何递增地数丑数?

观察上面的例子,假如我们用1, 2, 3, 4, 5, 6去形成后面的丑数,我们可以将1, 2, 3, 4, 5, 6分别乘以2, 3, 5,这样得到一共6*3=18个新丑数。也就是说1, 2, 3, 4, 5, 6中的每一个丑数都有一次机会与2相乘,一次机会与3相乘,一次机会与5相乘(一共有18次机会形成18个新丑数),来得到更大的一个丑数。

这样就可以用三个指针:

p2, 指向1, 2, 3, 4, 5, 6中,还没使用 乘2机会 的丑数的位置。该指针的前一位已经使用完了乘以2的机会。
p3, 指向1, 2, 3, 4, 5, 6中,还没使用 乘3机会 的丑数的位置。该指针的前一位已经使用完了乘以3的机会。
p5, 指向1, 2, 3, 4, 5, 6中,还没使用 乘5机会 的丑数的位置。该指针的前一位已经使用完了乘以5的机会。

下一次寻找丑数时,则对这三个位置分别尝试使用一次乘2机会,乘3机会,乘5机会,看看哪个最小,最小的那个就是下一个丑数。最后,那个得到下一个丑数的指针位置加一,因为它对应的那次乘法机会使用完了

这里需要注意下去重的问题,如果某次寻找丑数,找到了下一个丑数10,则p2和p5都需要加一,因为5乘2等于10, 2乘5也等于10,这样可以确保10只被数一次(所以不能使用 if ... else if;要用三个if判断)。

C++实现

class Solution
{
public:inline int _min(int n1,int n2,int n3){return n1>n2?(n2>n3?n3:n2):(n1>n3?n3:n1);}public:int nthUglyNumber(int n) {if(n<=1)return n;vector<int> dp(n+1);dp[1]=1;int p2,p3,p5;p2=p3=p5=1; //初始化三个指针,都指向第一个元素,因为第一个元素还没有使用分别乘2、乘3、乘5的机会for(int i=2;i<=n;++i){int num2=dp[p2]*2;int num3=dp[p3]*3;int num5=dp[p5]*5;dp[i]=_min(num2,num3,num5);if(dp[i]==num2)p2++;   //p2所指向的元素已经使用完 乘2 这个机会了if(dp[i]==num3)p3++;   //p3所指向的元素已经使用完 乘3 这个机会了if(dp[i]==num5)p5++;   //p5所指向的元素已经使用完 乘5 这个机会了  }return dp[n];}
};

剑指Offer49—丑数相关推荐

  1. 剑指Offer丑数问题

    这是剑指第一次卡死我的题--记录一下 首先看题目: 把只包含质因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含质因子7. 习惯上我们把1当做是第一个丑数 ...

  2. 剑指offer—丑数

    题目描述 把只包含因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含因子7. 习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第N个丑数. publi ...

  3. 剑指offer——丑数

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

  4. 在线编程——丑数系列(pythonC++)

    在线编程--丑数系列(python&C++) 丑数定义:"丑数"(ugly number)是正数,且质数因子只包含2.3.5.例如6,8是丑数,但14不是丑数,因为它包含因 ...

  5. 【LeetCode笔记】263. 丑数(Java、迭代)

    文章目录 题目描述 思路 & 代码 题目描述 打卡每日一题-(当然今天比较简单才写的) 第一次写可能有点懵(指对"丑数"这个概念不太理解) 不过写完后,感觉是基本上不会忘记 ...

  6. 【前端js】实现剑指offer|leetcode(二)——数组题目集合

    文章目录 一.数组去重 1. 对排序数组去重(leetcode 26. 删除排序数组中的重复项) 2. 检查是否存在重复元素(leetcode 217. 存在重复元素) 3. 检查相邻k个元素是否存在 ...

  7. 剑指offer:丑数

    题目描述 把只包含质因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含质因子7. 习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第N个丑数. 解题思 ...

  8. 【剑指offer】丑数

    把只包含因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含因子7. 习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第N个丑数. leetcode上也 ...

  9. 《剑指offer》-- 把数组排成最小的数、丑数、二进制中1的个数、表示数值的字符串、替换空格

    一.把数组排成最小的数: 1.题目: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为 ...

最新文章

  1. 结构化综合布线系统中的干线子系统是指(33)。【答案】D
  2. 丽水学院计算机科学与技术,丽水学院计算机科学与技术专业2016年在河南理科高考录取最低分数线...
  3. Linux 服务器配置信息查询方法,国产化申威服务器配置信息查看演示
  4. ASP.NET传统的三层架构
  5. com.fasterxml.jackson将对象序列化成json时,出现在json里的属性名称是怎么来的
  6. 如何修改 asp.net core 5 程序的默认端口号?
  7. groupby java_Java8的groupBy实现集合的分组
  8. oracle xsql 详解(一)
  9. 编译OpenJDK8:configure: error: Could not find all X11 headers
  10. 黑桃k游戏java实战_Java入门第三季项目实战——扑克游戏
  11. 详解 ManualResetEvent(转)
  12. CellCtrl控件完美破解研究
  13. uwsgi 的启动、停止、重启
  14. “数据分析师”招聘信息的数据分析
  15. 愚人节导入_在愚人节的恶作剧破坏之后,如何重置键盘的映射?
  16. 加盟连锁如何降低风险?
  17. python 脚本下载bilibili右上角表情包
  18. 刘德华陈年温馨情侣照(多图)
  19. vue 鼠标拖动画矩形_vue中拖动元素效果实现,以及拖拽`缩放后的元素`效果实现...
  20. JS——对已有元素内容进行筛选(本地搜索筛选)

热门文章

  1. 解决linux下 MySQL密码忘记的问题
  2. 湖南省隆回县2011年下学期高二调研试卷语文
  3. python飞机如何使用机器人
  4. 如何写SCI文章-转自知乎
  5. GeoServer发布osm数据地图服务
  6. 蜘蛛侠该怎么做才能救到格温(processing)
  7. 什么是EEPROM?和ROM有区别吗?//2021-2-18
  8. 日记侠:都是精准引流的文章,前3名阅读量怎么差这么多
  9. 计算机数据恢复试题,数据恢复试卷含笔答实操题a
  10. MFC---定时器和双缓冲机制绘制旋转的金刚石图案