题目描述:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数
看到题目,第一反应就是暴力,但是我试了一下,直接爆内存。这里我参考了别人的代码,然后反向推导出这题的理解思路和解题过程。看了下文,你会和我一样惊叹这世上竟有如此神奇的代码和如此精妙的思路!
分析题意:题目很简单,就是将所有只包含素因子的数叫做丑数,比如1 2 3 4 5 6 8 9 10 12 15 16 18...那么,我们是不是可以这样理解,就是求所有的N=(2^i) * (3^j) * (5^k) (其中i,j,k表示正整数),最后排序N,求指定下标的N.
经过上面的分析,我们成功的将问题转化了,现在问题来了,N的大小有规律可言吗?简单的测一下:
i j k N
1 0 0 2
0 1 0 3
0 0 1 5
1 1 0 6
1 0 1 10
0 1 1 15
1 1 1 30
2 0 0 4
2 1 0 12
我们发现,好像没有什么规律而言,也确实没什么规律可言。所以,就不用再花时间来琢磨这不太容易发现的规律。依据题意,我们需要对N进行排序,如果是找到所有数据再排序,效率明显太低,所以我们要想办法找到一个数据就把它放在正确的位置。那要如何实现呢?
每次找到数据,都必须保证该数据是所有可能中最小的数据。那么每次比较的所有可能数据有哪些呢?
1 2 3 4 5 6 8 9 10 12 15 16 18...它们每个数都会和2 3 5相乘,然后决策出下一个数字是什么。但是事实上,并不是下一个数并不是和以上的每一个都相关.举个例子:18,与1 2 3 4无关,与8 10 12 15无关,因为它们2 3 5中任意一个相乘都不可能得到20。比如18下一个丑数将会是20,其实这个而是与1 2 3 6无关,与8 9 12 15 16 18也无关再比如20后面的数将是24,与1 2 3 4 5无关,与9 10 15 16 18 20也无关。我们可以对比一下18,20和24:
18 ==>6*3 9*2 20 ==>4*5 10*2 24 ==>8*3 12*2 似乎可以看到与2相乘的数在不停的增大,与3相乘的数也是如此,为了验证这种想法,我们多列举几个:
25==>5*5 27==>9*3 30==>10*3 15*2 32==>16*2
相信到了这里,我们就又可以大胆推测了,下一个数的出现,是从之前部分数据与2,3,5的乘积中取出最小的一个,而且这部分数据会逐渐向后移动,而且我们发现,与2相乘的数大于与3相乘的数,与3相乘的数大于与5相乘的数。
到了这一步,我们就会很自然的想到一个东西了--下标!下标不就是记录当前的位置的吗?因为是从与2 3 5的乘积中选出最小的数,所有我们设置三个下标来记录它们的移动路径,分别为t2 t3 t5(t2>=t3>=t5)。每次找到一个新的数,就将对应的下标+1,总的下标也+1.例如:
N=25,在此之前t5=4,取出N=25,t5=5,t5对应的数变成了6.
然后每次查找下一个数时,根据原策略需要分别比较每个数字与2 3 5的乘积的大小。但是事实上,每个位置上的数都会经历与2 3 5相乘,而且它们不应该同时比较。什么意思呢?简单点说根据最优策略,最大的数应该与2相乘,其次与3相乘,最末与5相乘。t2>=t3>=t5,t2位置上的数会分别与2 3 5相乘,而与3 5相乘时,t2已经在后面了,t3或t5变成了此时的t2。所以,我们比较的是t2*2 与 t3*3 与t5*5之间的大小,取出它们之间最小值,当作下一个丑数。这样就能保证每次取到的数都是最小的丑数。
附上源码一份:
class Solution {
public:
int GetUglyNumber_Solution(int index) {
int t2=0,t3=0,t5=0,i=1;
if(index<7)
return index;
vector<int> a(index);
a[0]=1;
for(;i<index;i++){
a[i]=min(a[t2]*2,min(a[t3]*3,a[t5]*5));
if(a[i]==a[t2]*2) t2++;
if(a[i]==a[t3]*3) t3++;
if(a[i]==a[t5]*5) t5++;
}
return a[index-1];
}
};