经典面试题目——找到第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.经历这半年的洗礼,自己的综合能力和素质都得到了一个质的提升. 实话说对于未 ...
随机推荐
- [LeetCode&Python] Problem 530. Minimum Absolute Difference in BST
Given a binary search tree with non-negative values, find the minimum absolute difference between va ...
- BZOJ 5099: Pionek(双指针)(占位)
pro:有N个向量,你可以选择一些向量,使得其向量和离原点最远. 输出这个欧几里得距离的平方. sol:(感觉网上的证明都不是很充分,我自己也是半信半疑吧)日后证明了再补. #include<b ...
- python三大框架之一(flask介绍)
Flask , Django, Tornado 是python中常用的框架,也是python的三大框架.它们的区别是:Flask: 轻量级框架: Django:重量级框架: Tornado:性能最好 ...
- xdoj--1077: (循环节长度)
1077: 循环节长度 时间限制: 1 Sec 内存限制: 128 MB提交: 103 解决: 37[提交][状态][讨论版] 题目描述 数一有很多的有理数,然而有的是有限小数,如1/2=0.5, ...
- asm.js 和 Emscripten 入门教程
http://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html
- 第一次Scrum会议(10/13)【欢迎来怼】
一.小组信息 队名:欢迎来怼 小组成员 队长:田继平 成员:李圆圆,葛美义,王伟东,姜珊,邵朔,冉华 小组照片 二.开会信息 时间:2017/10/13 16:22~16:47,总计25min. 地点 ...
- QuickSort(Java)
private void quickSort(int[] input, int start, int end) { if (start >= end) return; int index = p ...
- 《DSP using MATLAB》Problem 5.10
代码: 第1小题: %% ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ %% Out ...
- readv与writev
[root@bogon mycode]# cat writev.c #include<stdio.h> #include<string.h> #include<unist ...
- day 54 JS 之 jquery
jQuery入门 2017-07-10 jQuery快速入门. jQuery介绍 jQuery是一个轻量级的.兼容多浏览器的JavaScript库. jQuery使用户能够更方便地处理HTML Doc ...