什么是丑数呢?丑数就是只包含因子2,3,5的数称作是丑数,在这里这种定义我是比较模糊的,所以上网查找了更加浅显的定义,丑数就是另一个丑数乘以2,3,5以后的结果(1除外,通常认为1是最小的丑数)。
通常,最简单的方法肯定是遍历数组,然后确定每一个数是否是丑数,这种方法是最简单并且直观的。
<span style="font-family:Microsoft YaHei;font-size:14px;">bool IsUgly(int num)
{
while (num % 2 == 0)
num /= 2;
while (num % 3 == 0)
num /= 3;
while (num % 5 == 0)
num /= 5;
if (num == 1)
return true;
else
return false;
}
int GetNUgly(int n)
{
if (n <= 0)
return 0;
int count = 0;
int num = 0;
while (count < n)
{
num++;
if (IsUgly(num))
{
count++;
}
}
return num;
}</span>
使用模除法判断一个数是不是丑数的时间效率是O(n*log n)。我们需要更加高效的算法。
根据丑数的定义,一个丑数必定是由另外一个丑数得来。这就是提升效率的地方,我们在上一种方法中浪费了太多的时间在判断不是丑数的数的上面,我们优化的地方就是,使用一个数组,里面放都是丑数,数组后面的数必定是前面的数乘以2,3,5得来的,关键就是确保数组里的丑数是有序的,
以下是构建有序丑数序列的算法:
我们把现有的最大丑数记做M。现在我们来生成下一个丑数,该丑数肯定是前面某一个丑数乘以2、3或者5的结果。
我们首先考虑把已有的每个丑数乘以2。在乘以2的时候,能得到若干个结果小于或等于M的。由于我们是按照顺序生成的,小于或者等于M肯定已经在数组中了,我们不需再次考虑;我们还会得到若干个大于M的结果,但我们只需要第一个大于M的结果,因为我们希望丑数是按从小到大顺序生成的,其他更大的结果我们以后再说。我们把得到的第一个乘以2后大于M的结果,记为M2。
同样我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么下一个丑数应该是M2、M3和M5三个数的最小者。
<span style="font-family:Microsoft YaHei;font-size:14px;">#include <iostream>
using namespace std;
int Min(int a, int b, int c)
{
int temp = (a < b ? a : b);
return (temp < c ? temp : c);
}
int FindUgly(int n) //
{
int* ugly = new int[n];
ugly[0] = 1;
int index2 = 0;
int index3 = 0;
int index5 = 0;
int index = 1;
while (index < n)
{
int val = Min(ugly[index2]*2, ugly[index3]*3, ugly[index5]*5); //竞争产生下一个丑数 (最小)
if (val == ugly[index2]*2) //将产生这个丑数的index*向后挪一位;
++index2;
if (val == ugly[index3]*3) //这里不能用elseif,因为可能有两个最小值,这时都要挪动;
++index3;
if (val == ugly[index5]*5)
++index5;
ugly[index++] = val;
}
</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> int result = ugly[n-1];
delete[] ugly;
return result;
}
int main()
{
int num;
cout << "input the number : " ;
cin >> num;
cout << FindUgly(num) << endl;
return 0;
} </span>
与一种方法相比,这种算法不会在非丑数上浪费时间,效率肯定提升,但是,凡是有利有弊,次算法需要new出一个1500大的数组,浪费了6 KB左右的内存,所以这是以空间换取时间的典型。
通过测试,第一种方法的找到第1500个丑数大概需要50s左右(VS2013),而第二种方法只需要3s左右。