#include
#include
#include
int Number_Chou(int num)
{
while(num%2 == 0)
num /= 2;
while(num%3 == 0)
num /= 3;
while(num%5 == 0)
num /= 5;
return (num == 1) ? 1:0;//最后商为1则返回;
}
int Search_Chou(int num)
{
int count = 0;
int steps = 0;
if(num <= 0)
return 0;
while(count < num)
{
++steps;//当前操作数
if(Number_Chou(steps))
count++;//丑数个数
}
return steps;
}
int main()
{
int start = GetTickCount();//计时函数
int end = 0;
int num = 1500;
int ret = Search_Chou(num);
end = GetTickCount();
printf("所用时间:%d秒\n\n所求丑数为:%d\n\n",(end - start)/1000,ret);
system("pause");
return 0;
}
如前所述,我们发现采用遍历法求第K个丑数的效率十分低下,我们在前面求第1500个丑数花去了33秒的时间,这还
是在我I7 3770K的电脑上运行的。所以我们考虑有没有一种更加高效的方法。在面试题9:斐波那契数列中我们使用
了一种“用空间还时间”的方法来提高求斐波那契数列的速度。这种编程思想也可以应用在这道题目当中,我们为所有求出的丑数创建数组,不在非丑数上面浪费时间。
根据丑数的定义,我们可以知道丑数可以由另外一个丑数乘以2,3或者5得到。因此我们创建一个数组,里面的数字
是排好序的丑数,每一个丑数都是前面的丑数乘以2,3或者5得到的。这种思路的关键在于怎样确保数组里面的数字
是排序的。
假设丑数数组中已经有若干个排好序的丑数,比如1,2,3,4,5。我们把当前丑数数组中的最大数记为M,这里
M=5。我们接下来分析如何生成下一个丑数。根据前面的介绍,我们知道这个丑数肯定是前面丑数数组中的数字乘以
2,3,5得到的。所以我们首先考虑把已有的每个丑数乘以2,在乘以2的时候,能够得到若干个小于或者等于M的结
果。由于是按照顺序生成的,小于或者等于M的数肯定已经在丑数数组当中了,我们不需要再次考虑;当然还会得到
若干大于M的结果,但是我们只需要第一个大于M的结果,因为我们希望丑数是按顺序排列的,所以其他更大的结果
可以以后考虑。我们把得到的第一个乘以2以后得到的大于M的结果记为M2。同样,我们把已有的每一个丑数乘以3和
5,能得到第一个大于M的结果M3和M5。那么M后面的那一个丑数应该是M2,M3和M5当中的最小值:Min
(M2,M3,M5)。比如将丑数数组中的数字按从小到大乘以2,直到得到第一个大于M的数为止,那么应该是2*2=4
3*2=6>M,所以M2=6。同理,M3=6,M5=10。所以下一个丑数应该是6。
前面分析的时候,提到把已有的每个丑数分别都乘以2,3和5。事实上这不是必须的,因为已有的丑数是按顺序存放
在数组中的,对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于等于(<=)已
有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会大于已有的最大丑数。因此我们只需要记下这个丑数的
位置,同时每次生成新的丑数的时候去更新这个T2。对于乘以3和5,同样存在这样的T3和T5。
#基于上面的思路我们有可以写下面的代码:
#include
#include
int Min(int num1,int num2,int num3)
{
int min = (num1 < num2) ? num1 : num2;
min = (min < num3) ? min : num3;
return min;
}
int Search_Chou(int num)
{
int ret = 0;
int change = 1;
int min = 0;
int *ob2 = NULL;
int *ob3 = NULL;
int *ob5 = NULL;
int *ptr = (int *)malloc(num*sizeof(int));
if(ptr == NULL)
printf("out of memery\n");
if(num<0)
return 0;
ptr[0] = 1;
ob2 = ptr;
ob3 = ptr;
ob5 = ptr;
while(change < num)
{
min = Min((*ob2)*2,(*ob3)*3,(*ob5)*5);
ptr[change] = min;//最小的放进去
while((*ob2)*2 <= ptr[change])//注意这里都是小于等于
++ob2;//记录当前位置
while((*ob3)*3 <= ptr[change])
++ob3;//记录当前位置
while((*ob5)*5 <=ptr[change])
++ob5;//记录当前位置
++change;
}
ret = ptr[change - 1];//注意返回值的下标;
free(ptr);//记得释放
ptr = NULL;
return ret;
}
int main()
{
int num = 1500;
int ret = 0;
ret = Search_Chou(num);
printf("所求丑数为:%d\n",ret);
system("pause");
return 0;
}
#结果瞬间出来,效率可见一斑;
# 基于上述思路,在下面列出一种便于理解的代码,与上述思路一样,作为参考;
int Search_Chou()
{
int num[]=new int[1501];
num[0] = 0;
num[1] = 1;
int two = 0,three = 0,five = 0;//刚好大于现有最大丑数的三个
for(int i = 1; i <= 1499; i++)
{//每次找出一个丑数
for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的两倍刚好大于上回找出的丑数,将它值记录下来
if((2*num[j-1] <= num[i])&&(2*num[j] > num[i])) two=num[j]*2;
for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的三倍刚好大于上回找出的丑数,将它值记录下来
if((3*num[j-1] <= num[i])&&(3*num[j] > num[i])) three=num[j]*3;
for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的五倍刚好大于上回找出的丑数,将它值记录下来
if((5*num[j-1] <= num[i])&&(5*num[j] > num[i])) five=num[j]*5;
//比较出三个数中最小的
if(two>=three&&five>=three) num[i+1] = three;
else if(two>=five&&three>=five) num[i+1] = five;
else if(five>=two&&three>=two) num[i+1] = two;
}
return num[i+1];
}
@ 关于丑数这个神奇的概念就说到这里,在这里给大家推荐一本书《编程之美》,里面的解体思路都是由简入深,非
常适合培养我们的解题思路!
Ps: 爱拼才会赢!
Thanks!