问题描述:
我们把只包含因子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),效率上肯定是有折扣的了,下面是代码:
- #include <iostream>
- #include <climits>
- 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),则可以得到以下代码:
- #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;
- }
- /*
- 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分法进行缩小范围,直至求出解。
- #include <set>
- #include <iostream>
- #include <climits>
- 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++版本,代码如下:
- #include <iostream>
- 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;
- }
总结起来,就是最简陋的遍历,从小到大的只算丑数,统计全部丑数,计算丑数个数,方法不同,算起来,搞程序还是很有意思的嘛,可惜没早点发现,就这样了吧。