首先,我们来看下这道面试题的描述:
把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
法一:遍历法
要求第K个丑数,那么根据它的性质(只含有2,3,5的因子),我们可以想到一种很直观的方法:
从1开始每次加一往后找,知道找到K个丑数,每一次判断该数是否为丑数的方法是——看该数是否能被2整除,若能则一直除2;然后再看能否被3整除,若能则一直出3;5也做同样的处理。这样看最后剩下的数是否为1,若为1则该数是丑数。
于是我们可以很快写出如下代码:
int GetUglyNumber_Solution(int index)
{
if(index == 1)
{
return 1;
}
int start = 1;
for(int i = 2; i <= index; ++i)
{
start++;
while(!IsUglyNum(start))
{
start++;
}
}
return start;
}
bool IsUglyNum(int num)
{
while(num % 2 == 0)
{
num /= 2;
}
while(num % 3 == 0)
{
num /= 3;
}
while(num % 5 == 0)
{
num /= 5;
}
if(num != 1)
{
return false;
}
return true;
}
在本地一跑,结果一般会对着,但是当你把输入的数比较大时,如1500,那么你会发现:结果要等上个5~6秒的样子。然后在OJ上提交,会发现根本通不过,超时。显然,时间复杂度太高。那么该如何优化呢,当然是我们的空间换时间,这类的题都这样去想就OK了。下面看第二种方法。
法二:动态规划
*算法分析:*
用一个表table,把0到K-1个丑数存起来,然后求第K个丑数时,可以用前面求出来的丑数,做一个遍历,分别计算该丑数乘以2,3,5是否大于第K-1个丑数的值,取最小的那个大于K-1的数,必定为第K个丑数。
但是这一块的难点在于:在table中找min时的逻辑处理,这一块很难讲清楚,需要看官自己去理清楚。举个例子,在求第4个丑数时,按照算法,我们会先求得1X2,1X3, 1X5,然后这时候5已经大于第3个丑数了;但是我们会发现这时候还不能出循环,因为 接下来2X2也大于第3个丑数,而且4小于5。那么在得到的min为某个丑数乘以3或者5得到时我们还要继续往后找,看是否下一个丑数乘以2,或3 比它更小。 代码如下:
int GetUglyNumber_Solution(int index)
{
vector<int> table;
table.push_back(1);
if(index == 1)
{
return 1;
}
for(int k = 2; k <= index; ++k )
{
int min = INT_MAX;
for(int i = 0; i < k-1; ++i) //table 的下标
{
if(table[i] * 2 > table[k-2])
{
if(table[i] * 2 < min)
{
min = table[i] * 2;
}
break;
}
else if(table[i] * 3 > table[k - 2])
{
if(table[i] * 3 < min)
{
min = table[i] * 3;
}
}
else if(table[i] * 5 > table[k -2])
{
if(table[i] * 5 < min)
{
min = table[i] * 5;
}
}
}
table.push_back(min);
}
return table[index-1];
}
当我们输入1500时大概只要4ms就可出结果了。