问题描述:
我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第1500个丑数。(昨天突然发现个不错的博客:http://blog.csdn.net/v_JULY_v,突然知道丑数这个题,于是搜之)
当然,最简单的肯定是遍历啊,想当年初学的时候,什么水仙花数,完数,质数,都遍历搞定。遍历存在的问题就是效率太低,如同暴力破密码似的,以前用bt4破一个wep的有时候都要10多分钟,破个WAP加密的半个小时,这不蛋疼吗,破了就为蹭个网。像这个吧,到第1500个丑数的时候,用时就要42s多(win7+vc6),效率上肯定是有折扣的了,下面是代码:
-
-
-
using namespace std;
-
//遍历法找丑数
-
int 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 1;
-
else
-
return 0; //not an ugly number
-
}
-
void GetUglyNumber(int index)
-
{ //找到第index个丑数
-
int i , time = 0 ;
-
if (index < 1)
-
{
-
cout << "error input " << endl;
-
exit(EXIT_FAILURE);
-
}
-
for (i= 1 ; i< INT_MAX && time < index ; i++)
-
{
-
if ( IsUgly(i) )
-
{
-
time ++ ;
-
// cout << i << " " ;
-
}
-
}
-
cout << i -1 << endl;
-
}
-
int main()
-
{
-
int Number;
-
cout << "Input a number : " ;
-
cin >> Number ;
-
GetUglyNumber(Number);
-
return 0;
-
}
根据丑数的定义,丑数应该是另一个丑数乘以2、3或者5的结果(1除外)。因此我们可以创建一个数组,里面的数字是排好序的丑数。里面的每一个丑数是前面的丑数乘以2、3或者5得到的。那关键就是确保数组里的丑数是有序的了。我们假设数组中已经有若干个丑数,排好序后存在数组中。我们把现有的最大丑数记做M。
现在我们来生成下一个丑数,该丑数肯定是前面某一个丑数乘以2、3或者5的结果。
我们首先考虑把已有的每个丑数乘以2。在乘以2的时候,能得到若干个结果小于或等于M的。
由于我们是按照顺序生成的,小于或者等于M肯定已经在数组中了,我们不需再次考虑;
我们还会得到若干个大于M的结果,但我们只需要第一个大于M的结果,因为我们希望丑数是按从小到大顺序生成的,其他更大的结果我们以后再说。
我们把得到的第一个乘以2后大于M的结果,记为M2。
同样我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么下一个丑数应该是M2、M3和M5三个数的最小者。(来自http://www.cnblogs.com/mingzi/archive/2009/08/04/1538491.html),则可以得到以下代码:
-
-
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;
-
}
-
/*
-
for (int i = 0; i < n; ++i)
-
cout << ugly[i] << endl;
-
//*/
-
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;
-
}
另外还可以采用的方法很多,参考 http://www.iteye.com/topic/832545 。本帖子列出了5种方法:
- * 1.method1是最基础的遍历,唯一的优点估计就是简单易懂。<br/>
- * 2.method2,method3的思想是先人工估算范围值,将一定范围内的值乘2,3,5排重增加,不同的地方在于method2重新遍历,
- * method3排序求下标<br/>
- * 3.method4的思想是将已经获取的值分别遍历,乘以2,3,5,当比最大值大就停止,比较这3个数的最小值,增加到定义的有序数组中。<br/>
- * 4.method5的思想是将数进行评估,评估出该数包含丑数的数量,当超过丑数要求数量时,进行2分法进行缩小范围,直至求出解。
-
-
-
-
using namespace std;
-
const int MAX = INT_MAX/ 5;
-
-
void GetUgly(int Index)
-
{
-
int i;
-
set< int,less< int> > s;
-
set< int, less< int> >::iterator It;
-
s.insert( 1);
-
for (i= 1 ; i<MAX ; i++)
-
{
-
if (s.find(i) != s.end() )
-
{
-
s.insert( 2*i) ;
-
s.insert( 3*i) ;
-
s.insert( 5*i) ;
-
}
-
}
-
for (It = s.begin() ,i= 1 ; It != s.end() && i < Index; It++)
-
i++;
-
cout << *It << endl;
-
}
-
int main(int argc,char *argv[])
-
{
-
int Number;
-
cout << "Input a number : " ;
-
cin >> Number ;
-
GetUgly(Number);
-
return 0;
-
}
下面来改写Java的method5为C++版本,代码如下:
-
-
using namespace std;
-
-
int nums5(int val)
-
{
-
int n= 0 ;
-
while (val >= 5)
-
{
-
n++ ;
-
val /= 5;
-
}
-
return n;
-
}
-
int nums35(int val)
-
{
-
int n= 0 ;
-
while (val >= 3)
-
{
-
n += 1+nums5(val);
-
val /= 3;
-
}
-
return n;
-
}
-
//基于因数分解求出val以内有多少个丑数(不包含1)
-
int nums235(int val)
-
{
-
int n= 0 ;
-
while (val >= 2)
-
{
-
n += 1+nums35(val);
-
val /= 2 ;
-
}
-
return n;
-
}
-
//用二分法查找第n个丑数
-
//对于X,如果X以内的丑数个数是n,而X-1以内的丑数个数是n-1,那么X就是第n个丑数
-
int numOfIndex(int n)
-
{
-
if(n == 1)
-
return 1;
-
n--;
-
int val1 = 1;
-
int nums1 = 0;
-
int val2 = 2;
-
int nums2 = nums235(val2); //nums2为val2的因数个数
-
while( nums2 < n )
-
{
-
val1 = val2;
-
nums1 = nums2;
-
val2 = val1* 2;
-
nums2 = nums235(val2);
-
}
-
if( nums1 == n )
-
return val1;
-
if( nums2 == n )
-
return val2;
-
-
while( true)
-
{
-
long mid = (val1 + val2)/ 2;
-
int nums = nums235(mid);
-
if(val2 == mid+ 1 && nums == n -1 && nums2==n)
-
return val2;
-
if(mid == val1+ 1 && nums1 == n -1 && nums==n)
-
return mid;
-
if(nums >= n)
-
{
-
val2 = mid;
-
nums2 = nums;
-
}
-
else
-
{
-
val1 = mid;
-
nums1 = nums;
-
}
-
}
-
}
-
int check(int val)
-
{
-
long v = val;
-
while( v% 2== 0 )
-
v/= 2;
-
while( v% 3== 0 )
-
v/= 3;
-
while( v% 5== 0 )
-
v/= 5;
-
if( v != 1 )
-
cout << " v is not an ugly number! " << endl;
-
return val;
-
}
-
-
void calc(int n)
-
{
-
long val = numOfIndex(n);
-
cout << n << " : " << val << endl;;
-
check(val);
-
}
-
-
int main(int argc ,char *argv[])
-
{
-
int Number;
-
cout << "Please input a number : " ;
-
cin >> Number ;
-
calc(Number);
-
return 0;
-
}
总结起来,就是最简陋的遍历,从小到大的只算丑数,统计全部丑数,计算丑数个数,方法不同,算起来,搞程序还是很有意思的嘛,可惜没早点发现,就这样了吧。
菜鸟goes on ~~