生活随笔
收集整理的这篇文章主要介绍了
面试题之丑数的C++实现求解(孤陋寡闻了,才知道丑数这么high的东东)
小编觉得挺不错的,现在分享给大家,帮大家做个参考.
问题描述:
我们把只包含因子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;}void GetUglyNumber(int 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-1 << endl;}int main(){ int Number; cout << "Input a number : " ; cin >> Number ;GetUglyNumber(Number); return 0;}
遍历法很大的问题在于对每个数都进行判断,进行取余和除的运算了,如果换种思路的话,只对丑数进行计算呢?根据
http://www.cnblogs.com/mingzi/archive/2009/08/04/1538491.html
的思路,虽然从代码上来看
http://www.cppblog.com/zenliang/articles/131094.html
的更简洁易懂,不过第一个链接的变量命名会好很多,而且思路交代更清晰。
根据丑数的定义,丑数应该是另一个丑数乘以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) ++index2; if (val == ugly[index3]*3) ++index3; if (val == ugly[index5]*5) ++index5; ugly[index++] = val; } 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.cppblog.com/zenliang/articles/131094.html
。看到他的new,才想起,以前写排序的时候,由于数组大小可变,直接用了vector,让它直接去vector的size()就知道大小了,而没有想到还有更初级的new,对于不定大小,new就好了啊,虽说new出来的是是在堆上,直接定义的是在栈上,不过用起来也是毫无影响的,果然自己还是太菜了点。
另外还可以采用的方法很多,参考
http://www.iteye.com/topic/832545
。本帖子列出了5种方法:
代码直接参考,实际上,搜到的C++的代码就是method1和method4,其实吧,method2和method3的精髓在于i < Integer.MAX_VALUE / 5 ,也是利用了所有丑数肯定是由丑数产生这一思想,虽然不同之处在于遍历和求下标,不过总体是产生足够大的丑数集合,再直接取需要的位置。C++实现如下:
#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;}
说到这个,本打算用vector的,还用到了algorithm头文件的find和sort。不过问题在于vector怎么删除重复元素呢?哪怕加入是否在vector中的判断,仍然难以阻止,效率不高。不过一不小心找到了STL的
set
,高级货啊,
set自动删除重复元素
这一特性,还是很给力的。和Java的set一样,不过这个算法的问题在于,直接将所有的丑数都找出来了,再取下标,在vc6和gcc测试下,速度着实很慢,莫非是C++STL的set不如Java的set高效么?这个方法让我想到对于1000个数,找出其中最小的5个,但是将这1000个数都进行排序了再直接取前5个,虽然可行,但未免开销太大,不经济。运行的时候,等的时间太长,以至于直接关掉,将MAX换为2w,随便测试了下对于100等数是否正确来判断程序是否大致准确。
下面来改写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;}int nums235(int val){ int n=0 ; while (val >= 2){n += 1+nums35(val);val /= 2 ;} return n;}int numOfIndex(int n) { if(n == 1) return 1; n--; int val1 = 1; int nums1 = 0; int val2 = 2; int nums2 = nums235(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;}
想不到这算法很是高级货啊,直接因数分解,其实也是充分利用丑数是由丑数产生这一原理,用nums235统计出val内丑数个数。虽然也是都大量计算,不过比第一种的好很多,加上引入二分查找,效率还是不错的。经过测试,与method4在1500的时候都能在5ms内完成,各有所长。不过有个不足的地方,
http://www.iteye.com/topic/832545
虽然说这方法是最优解(如果在calc中去掉check调用,1500-1545都是1ms或2ms完成,震惊啊),不过在输入1546开始,会很慢,更不用说在1692这样会溢出的点,会很慢(没等,不知道具体时间)不过在1545以内,的确是最优,作者
taolei0628
果然牛。
总结起来,就是最简陋的遍历,从小到大的只算丑数,统计全部丑数,计算丑数个数,方法不同,算起来,搞程序还是很有意思的嘛,可惜没早点发现,就这样了吧。
菜鸟goes on ~~
总结
以上是生活随笔为你收集整理的面试题之丑数的C++实现求解(孤陋寡闻了,才知道丑数这么high的东东)的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。