经典面试题目——找到第n个丑数(参考《剑指offer(第二版)》面试题49)
一.题目大意
给你一个数n,要求返回第n个丑数。其中,丑数的定义如下:
丑数是指只包含因子2、3和5的数。(数字1也是丑数,不过是个特例)引用《剑指offer》上的话来说,对于一个数M,如果M能被2整除,就连续除以2;若果能被3整除,就连续除以3;如果能被5整除,就连续除以5。如果最终的结果是1的话,那么M就是丑数,否则M不是丑数。以下是判断一个数是否为丑数的代码:
bool IsUglyNumber(int num)
{
while(num % 2 == 0)
num /= 2;
while(num % 3 == 0)
num /= 3;
while(num % 5 == 0)
num /= 5;
return (num == 1)? true : false; }
二.思路分析
方法1.
最直观的解法,就是从1开始进行遍历,对于每个数分别判断是否为丑数,直到找到n个丑数为止。
此方法,对于n比较大的情况,往往都会超时的。所以,这种方法并不最优选择。
方法2:
实质是对方法1的优化,分析方法1,会发现浪费的操作主要在于非丑数的数。所以想方设法对非丑数的数进行排除,即只对丑数进行操作。《剑指offer》上给了这样一种思路:
(1)首先,根据丑数的定义,任何一个丑数都应该是另一个丑数乘以2或者3或者5的结果(1除外)。因此,根据这种特性,我们可以从最小的丑数开始逐渐生成新的丑数,并且要求新生成的丑数是排好序的(升序),如果用一个数组来存储它们的话,那么数组的第n个元素就是我们想要的结果。
(2)这种方法的重点在于如何确保新生成的丑数是排好序的:
假设这个有序的丑数数组为A[0]、A[1]、A[2]、... A[M],那么,A[M + 1]一定是A[0]或A[1]或 ... A[M]中的某个元素(假设这个元素为A[i])乘以2或者乘以3或者乘以5得到的,至于A[i]具体是哪个值,我们现在是不知道的。不过我们可以有以下的分析:
1.我们首先考虑把A[0]、A[1]、A[2]、...A[M]这M+1个数都乘以2,那么我们就能得到M + 1个新的丑数,但是这M+1个丑数中,一定有一部分与A[0]、A[1]、A[2]、... A[M]中的某些数是重复的(这M+1个丑数中小于A[M]的数一定就是重复的,因为数组A就是有序的丑数序列),这些重复的丑数就不用考虑了,也一定有一部分数是大于A[M]的,而我们要找的就是从这些大于A[M]的丑数中,选出最小的一个来,并把这个最小的丑数命名为N2。
2.我们再把A[0]、A[1]、A[2]、...A[M]这M+1个数都乘以3,那么我们也能得到M + 1个新的丑数,这M+1个丑数中,同理,一定有一部分与A[0]、A[1]、A[2]、... A[M]中的某些数是重复的(这M+1个丑数中小于A[M]的数一定就是重复的),这些重复的丑数就不用考虑了,也一定有一部分数是大于A[M]的,而我们要找的就是从这些大于A[M]的丑数中,选出最小的一个来,并把这个最小的丑数命名为N3。
3.我们最后再把A[0]、A[1]、A[2]、...A[M]这M+1个数都乘以5,那么我们同样能得到M + 1个新的丑数,这M+1个丑数中,同理,一定也有一部分与A[0]、A[1]、A[2]、... A[M]中的某些数是重复的(这M+1个丑数中小于A[M]的数一定就是重复的),这些重复的丑数就不用考虑了,也一定有一部分数是大于A[M]的,而我们要找的就是从这些大于A[M]的丑数中,选出最小的一个来,并把这个最小的丑数命名为N5。
4.经过前3步,我们得到了三个最小的数N2、N3、N5,那么,A[M+1]一定是这三个数中最小的那个数。
5.我们就可以重复第1步~第4步,直到数组A中的元素达到n个时,A[n - 1]就是我们想要的结果。
注:我们在以上的理论分析中每次都需要把A[0]、A[1]、A[2]、... A[M](M在每次迭代后都会加1)分别乘以2、3和5,这样太麻烦了啊,能不能进一步优化呢?我们下面来详细分析下:
1.首先,我们要了解上面的第1~3步,主要是为了找出N2、N3、N5这三个数来,而实际上,只需找到原数组A(即已知的丑数序列)中对应的元素,令该元素乘以2或者乘以3或者乘以5就可以得到N2、N3或者N5了。以下进一步分析。
2.对于有序的丑数数组A[0]、A[1]、A[2]、... A[M],如果把数组中每个元素都乘以2的话,就会得到M+1个新的丑数,对于这M+1个新的丑数,我们之前已经给分析了,有一部分是与数组中原来的元素是重复的,另一部分则是大于A[M]的。所那么在原数组A(即已有的丑数)中,这M+1个旧的丑数中,一定存在这么一个丑数T2,排在它前面的丑数乘以2都小于等于A[M],排在它后面的丑数,都大于A[M]。那么,根据之前的1中的叙述,就可以得到T2 * 2 = N2。也就是说确定了T2,就能确定N2了。我们在进一步分析,如果N2、N3、N5这三个数中最小的是N2的话,那么A[M+1] = N2(令M1 =M +1),那么此时的T2就不满足之前的要求了(即排在它前面的丑数乘以2都小于等于A[M1],排在它后面的丑数,都大于A[M1],此时M1=M+1)所以此时,需要更新T2,即T2变成了原数组A中T2之后的那个元素(相当于下标索引加1)。每次选出相应的N值时,必须要更新相应的T值。
3.同理的话,我们根据1,可知同样在原数组A中可以得到一个丑数T5,使得排在它前面的丑数乘以5都小于等于A[M],排在它后面的丑数,都大于A[M]。同理可得,T5 * 5 = N5。同样的,如果N5是N2、N3、N5中最小的丑数的话,那么A[M+1] = N5,此时T5就更新成原数组A中T5之后的那个元素了。
4.同理,T3也是同样的更新准则。
5.综合1、2、3我们只需找到并更新T2、T3、T5的值即可,并不用把所有的已知丑数都乘一遍。(当然了,如果N2、N3、N5中存在两个相同的值(假设N2 == N3),并且都是最小的话,那么对应的两个数(T2和T3)都需要更新)。
6.由第5步可知,我们需要的是获得T2、T3、T5的初值,并对他们进行更新,就能获取排好序的数组之后的元素了(A[M+1]、A[M+2]、A[M+3]、... A[n-1])。那么如何获得T2、T3、T5的初始值?由于刚开始数组中的元素只有1个,即A[0] =1,此时的T2 = T3 = T5 =1的,所以按照之前的规则进行更新就可以了。
================================================================================================================================================
综合以上分析,可得代码如下:
int GetUglyNumber_Solution(int index) {
if(index < 7)
return index; //由于数字1~6都是丑数,而且是排好序的,所以可以直接返回
vector<int> rs(index);
rs[0] = 1;//初始化数组,1是个特例
int t2 =0, t3 = 0, t5 = 0;//rs[t2]对应T2,rs[t3]对应T3,rs[t5]对应T5
for(int i = 1; i < index; i++)
{
rs[i] = min(rs[t2] * 2,min(rs[t3] * 3,rs[t5] * 5));//选出T2、T3、T5中的最小值
if(rs[i] == rs[t2] * 2)
t2++;//更新T2,T2变成数组中T2之后的那个元素了
if(rs[i] == rs[t3] * 3)
t3++;
if(rs[i] == rs[t5] * 5)
t5++;
}
return rs[index - 1];
}
该方法的时间复杂度为O(n),空间复杂度O(n)。相比思路一时间效率得到了极大的提高。
观察改代码可知,实际上通过t2、t3、t5这三个索引下标就实现了对原数组中所有的元素乘以2、乘以3、乘以5的操作,只不过避免重复操作,才引入了T2、T3、T5这三个丑数。(一旦选定了N中的最小值,假设是N2,一定要去更新对应的T值,即N2对应的是T2)
可以具体分析下这段代码:
(1)首先,在第一轮循环中,数组rs中只有元素1,即rs[0] = 1,且t2 = t3 = t5 = 0,T2 = T3 =T5 = rs[0] = 1,由于N2是三者中最小值,故数组中新添加的元素rs[1] = N2 = 2。此时T2需要更新,即t2 = t2 +1(T2变成了T2之后的那个元素)
(2)在第二轮循环中,数组rs有两个元素了,且t2 =1,t3 = t5 = 0,T2 =rs[1] = 2,T3 = T5 = rs[0] = 1,由于N3是三者中的最小值,故数组中新添加的元素rs[2] = N3 = 3。此时T3需要更新,即t3 = t3 +1(T3变成了T3之后的那个元素)
(3)在第三轮循环中,数组rs有三个元素了,且t2 = t3 = 1,t5 = 0,T2 =T3 =rs[1] =2,T5 = rs[0] = 1,由于N2= T2 *2 =4,N3 = T3 * 3 = 6,T5= N5 * 5 = 5,N2是三者之中最小值,故数组中新添加的元素rs[3] = N2 =4,同时,T2需要更新,即t2 = t2 + 1
(4)在四轮循环汇总,数组rs有四个元素了,且t2 = 2,t3 = 1,t5 = 0,T2 = rs[2] = 3,T3 = rs[1] = 2,T5 = rs[0] = 1,由于N2 = 6,N3 = 6,N5 =5,即N5是三者之中的最小值,故数组中新添加的元素rs[4] = N5 = 5,同时,T5需要更新,即t5 = t5 +1
.......
按照此规律依次进行下去。
经典面试题目——找到第n个丑数(参考《剑指offer(第二版)》面试题49)的更多相关文章
- [互联网面试笔试汇总C/C++-9] 实现赋值运算符函数-剑指offer
题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数. class CMyString { public: CMyString(char* pData = NULL); CMyStr ...
- 【剑指Offer】剑指offer题目汇总
本文为<剑指Offer>刷题笔记的总结篇,花了两个多月的时间,将牛客网上<剑指Offer>的66道题刷了一遍,以博客的形式整理了一遍,这66道题属于相对基础的算法题目,对于 ...
- 面试题目——《剑指Offer》
1.把一个字符串转换成整数——<剑指Offer>P29 2.求链表中的倒数第k个结点——<剑指Offer>P30 3.实现Singleton模式——<剑指Offer> ...
- 33条C#、.Net经典面试题目及答案
33条C#..Net经典面试题目及答案[zt] 本文集中了多条常见的C#..Net经典面试题目例如".NET中类和结构的区别"."ASP.NET页面之间传递值的几种方式? ...
- 33条C#、.Net经典面试题目及答案[zt]
33条C#..Net经典面试题目及答案[zt] 本文集中了多条常见的C#..Net经典面试题目例如“.NET中类和结构的区别”.“ASP.NET页面之间传递值的几种方式?”,并简明扼要的给出了答案,希 ...
- 经典面试题目——250M内存处理10G大小的log文件
前言 周末逛知乎的时候,看到的一个经典面试题目:http://www.zhihu.com/question/26435483.非常经典的一道分而治之的题目. 题目描写叙述例如以下: 有次面试遇到一个问 ...
- 【剑指Offer面试编程题】题目1214:丑数--九度OJ
把只包含因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含因子7. 习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第N个丑数. 输入: 输入包括一个 ...
- 【强烈推荐】《剑指Offer:名企面试官精讲典型编程题》一书中IT名企经典面试题
各位程序猿: <剑指Offer>一书源自该书作者何海涛坚持更新与编写的博客(http://zhedahht.blog.163.com/),该博客收集整理了大量如微软.Goo ...
- 面试经典算法题集锦——《剑指 offer》小结
从今年 3 月份开始准备找实习,到现在校招结束,申请的工作均为机器学习/数据挖掘算法相关职位,也拿到了几个 sp offer.经历这半年的洗礼,自己的综合能力和素质都得到了一个质的提升. 实话说对于未 ...
随机推荐
- 改变html中的内容
$("#id").html() 获取内容 $("#id").html(xiugai) 修改内容
- Sublime text3常用的插件功能和常用的快捷键
Sublime text3常用的插件功能和用法 Package control 插件管理 (使用ctrl+` 将代码复制后粘贴到代码粘贴处,按Enter没有出现错误的话就安装成功了)(ctrl+shi ...
- Python基础练习及答案
1.请用代码实现:利用下划线将列表的每一个元素拼接成字符串,li=['alex', 'eric', 'rain'] 该题目主要是考的字符串的拼接,join方法, s = "" li ...
- 【mybatis源码学习】mybtias基础组件-占位符解析器
一.占位符解析器源码 1.占位符解析器实现的目标 通过解析字符串中指定前后缀中的字符,并完成相应的功能. 在mybtias中的应用,主要是为了解析Mapper的xml中的sql语句#{}中的内容,识别 ...
- CH4908 Race
题意 4908 Race 0x49「数据结构进阶」练习 描述 给定一棵 N 个节点的树,每条边带有一个权值. 求一条简单路径,路径上各条边的权值和等于K,且路径包含的边的数量最少. 输入格式 第一行两 ...
- mtail 部署说明
了解一个工具最好的方式是先--help 下,看看支持的命令以及参数 启动mtail 最基本的参数: --logs 支持需要处理的log 文件,支持通过glob 模式的额查找,可以指定多次 --prog ...
- 数据格式转换(一)PDF转换技术
PDF(Portable Document Format)文件格式是Adobe公司开发的电子文件格式. 这样的文件格式与操作系统平台无关.这一特点使它成为在Internet上进行电子文档发行 ...
- Appscan
IBM AppScan该产品是一个领先的 Web 应用安全测试工具,曾以 Watchfire AppScan 的名称享誉业界.Rational AppScan 可自动化 Web 应用的安全漏洞评估工作 ...
- 第1节 常用DOS(磁盘操作系统)命令
一.打开DOS命令窗口 1)快捷键:win + r,打开命令提示符窗口: 2)左击“开始”菜单,在运行里输入cmd,按回车打开命令提示符窗口: 二.常见命令 1)文件夹操作: d:+ 回车:盘符切换 ...
- touch-action 解决移动端300ms延迟问题
CSS3 新属性, touch-action: manipulation; 可以有效的解决移动端300ms延迟的问题 移动端300ms延迟问题一直都是h5APP的痛点, 有很多库或者方法都可以解决, ...