问题描述:

我们把只包含因子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),效率上肯定是有折扣的了,下面是代码:

  1. #include <iostream>
  2. #include <climits>
  3. using namespace std;
  4. //遍历法找丑数
  5. int IsUgly(int num)//判断是否是
  6. {
  7. while (num %2 == 0)
  8. {
  9. num /= 2;
  10. }
  11. while (num %3 == 0)
  12. {
  13. num /= 3;
  14. }
  15. while (num %5 == 0)
  16. {
  17. num /= 5;
  18. }
  19. if (num == 1)
  20. return 1;
  21. else
  22. return 0;//not an ugly number
  23. }
  24. void GetUglyNumber(int index)
  25. {//找到第index个丑数
  26. int i , time =0 ;
  27. if (index < 1)
  28. {
  29. cout << "error input " << endl;
  30. exit(EXIT_FAILURE);
  31. }
  32. for (i=1 ; i< INT_MAX && time < index ; i++)
  33. {
  34. if ( IsUgly(i) )
  35. {
  36. time ++ ;
  37. // cout << i << " " ;
  38. }
  39. }
  40. cout << i-1 << endl;
  41. }
  42. int main()
  43. {
  44. int Number;
  45. cout << "Input a number : " ;
  46. cin >> Number ;
  47. GetUglyNumber(Number);
  48. return 0;
  49. }

遍历法很大的问题在于对每个数都进行判断,进行取余和除的运算了,如果换种思路的话,只对丑数进行计算呢?根据 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),则可以得到以下代码:

  1. #include <iostream>
  2. using namespace std;
  3. int Min(int a, int b, int c)
  4. {
  5. int temp = (a < b ? a : b);
  6. return (temp < c ? temp : c);
  7. }
  8. int FindUgly(int n) //
  9. {
  10. int* ugly = new int[n];
  11. ugly[0] = 1;
  12. int index2 = 0;
  13. int index3 = 0;
  14. int index5 = 0;
  15. int index = 1;
  16. while (index < n)
  17. {
  18. int val = Min(ugly[index2]*2, ugly[index3]*3, ugly[index5]*5); //竞争产生下一个丑数
  19. if (val == ugly[index2]*2) //将产生这个丑数的index*向后挪一位;
  20. ++index2;
  21. if (val == ugly[index3]*3) //这里不能用elseif,因为可能有两个最小值,这时都要挪动;
  22. ++index3;
  23. if (val == ugly[index5]*5)
  24. ++index5;
  25. ugly[index++] = val;
  26. }
  27. /*
  28. for (int i = 0; i < n; ++i)
  29. cout << ugly[i] << endl;
  30. //*/
  31. int result = ugly[n-1];
  32. delete[] ugly;
  33. return result;
  34. }
  35. int main()
  36. {
  37. int num;
  38. cout << "input the number : " ;
  39. cin >> num;
  40. cout << FindUgly(num) << endl;
  41. return 0;
  42. }

代码来自: http://www.cppblog.com/zenliang/articles/131094.html 。看到他的new,才想起,以前写排序的时候,由于数组大小可变,直接用了vector,让它直接去vector的size()就知道大小了,而没有想到还有更初级的new,对于不定大小,new就好了啊,虽说new出来的是是在堆上,直接定义的是在栈上,不过用起来也是毫无影响的,果然自己还是太菜了点。
      另外还可以采用的方法很多,参考 http://www.iteye.com/topic/832545 。本帖子列出了5种方法:

  1. * 1.method1是最基础的遍历,唯一的优点估计就是简单易懂。<br/>
  2. * 2.method2,method3的思想是先人工估算范围值,将一定范围内的值乘2,3,5排重增加,不同的地方在于method2重新遍历,
  3. * method3排序求下标<br/>
  4. * 3.method4的思想是将已经获取的值分别遍历,乘以2,3,5,当比最大值大就停止,比较这3个数的最小值,增加到定义的有序数组中。<br/>
  5. * 4.method5的思想是将数进行评估,评估出该数包含丑数的数量,当超过丑数要求数量时,进行2分法进行缩小范围,直至求出解。

代码直接参考,实际上,搜到的C++的代码就是method1和method4,其实吧,method2和method3的精髓在于i < Integer.MAX_VALUE / 5 ,也是利用了所有丑数肯定是由丑数产生这一思想,虽然不同之处在于遍历和求下标,不过总体是产生足够大的丑数集合,再直接取需要的位置。C++实现如下:

  1. #include <set>
  2. #include <iostream>
  3. #include <climits>
  4. using namespace std;
  5. const int MAX = INT_MAX/5;
  6. void GetUgly(int Index)
  7. {
  8. int i;
  9. set<int,less<int> > s;
  10. set<int, less<int> >::iterator It;
  11. s.insert(1);
  12. for (i=1 ; i<MAX ; i++)
  13. {
  14. if (s.find(i) != s.end() )
  15. {
  16. s.insert(2*i) ;
  17. s.insert(3*i) ;
  18. s.insert(5*i) ;
  19. }
  20. }
  21. for (It = s.begin() ,i=1 ; It != s.end() && i < Index; It++)
  22. i++;
  23. cout << *It << endl;
  24. }
  25. int main(int argc,char *argv[])
  26. {
  27. int Number;
  28. cout << "Input a number : " ;
  29. cin >> Number ;
  30. GetUgly(Number);
  31. return 0;
  32. }

说到这个,本打算用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++版本,代码如下:

  1. #include <iostream>
  2. using namespace std;
  3. int nums5(int val)
  4. {
  5. int n=0 ;
  6. while (val >= 5)
  7. {
  8. n++ ;
  9. val /= 5;
  10. }
  11. return n;
  12. }
  13. int nums35(int val)
  14. {
  15. int n=0 ;
  16. while (val >= 3)
  17. {
  18. n += 1+nums5(val);
  19. val /= 3;
  20. }
  21. return n;
  22. }
  23. //基于因数分解求出val以内有多少个丑数(不包含1)
  24. int nums235(int val)
  25. {
  26. int n=0 ;
  27. while (val >= 2)
  28. {
  29. n += 1+nums35(val);
  30. val /= 2 ;
  31. }
  32. return n;
  33. }
  34. //用二分法查找第n个丑数
  35. //对于X,如果X以内的丑数个数是n,而X-1以内的丑数个数是n-1,那么X就是第n个丑数
  36. int numOfIndex(int n)
  37. {
  38. if(n == 1)
  39. return 1;
  40. n--;
  41. int val1 = 1;
  42. int nums1 = 0;
  43. int val2 = 2;
  44. int nums2 = nums235(val2); //nums2为val2的因数个数
  45. while( nums2 < n )
  46. {
  47. val1 = val2;
  48. nums1 = nums2;
  49. val2 = val1*2;
  50. nums2 = nums235(val2);
  51. }
  52. if( nums1 == n )
  53. return val1;
  54. if( nums2 == n )
  55. return val2;
  56. while(true)
  57. {
  58. long mid = (val1 + val2)/2;
  59. int nums = nums235(mid);
  60. if(val2 == mid+1 && nums == n-1 && nums2==n)
  61. return val2;
  62. if(mid == val1+1 && nums1 == n-1 && nums==n)
  63. return mid;
  64. if(nums >= n)
  65. {
  66. val2 = mid;
  67. nums2 = nums;
  68. }
  69. else
  70. {
  71. val1 = mid;
  72. nums1 = nums;
  73. }
  74. }
  75. }
  76. int check(int val)
  77. {
  78. long v = val;
  79. while( v%2==0 )
  80. v/=2;
  81. while( v%3==0 )
  82. v/=3;
  83. while( v%5==0 )
  84. v/=5;
  85. if( v != 1 )
  86. cout << " v is not an ugly number! " << endl;
  87. return val;
  88. }
  89. void calc(int n)
  90. {
  91. long val = numOfIndex(n);
  92. cout << n << " : " << val << endl;;
  93. check(val);
  94. }
  95. int main(int argc ,char *argv[])
  96. {
  97. int Number;
  98. cout << "Please input a number : " ;
  99. cin >> Number ;
  100. calc(Number);
  101. return 0;
  102. }

想不到这算法很是高级货啊,直接因数分解,其实也是充分利用丑数是由丑数产生这一原理,用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的东东)相关推荐

  1. 【面试题】从123456中取出三个数,取出三个不同数的概率是多少?

    面试题目: 从123456中取出三个数,取出三个不同数的概率是多少? 答案: 随便取数个数: 6 * 6 * 6 = 216: 取出不同数的个数是:6 * 5 * 4 = 120 概率 = 120 / ...

  2. 代码随想录算法训练营第07天 | LeetCode 454.四数相加2,383. 赎金信,15. 三数之和,18. 四数之和,总结

    LeetCode [454. 四数相加 II] 题目:给你四个整数数组 nums1.nums2.nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足 ...

  3. (补)算法训练第七天|力扣454.四数相加II ,383. 赎金信,15. 三数之和,18. 四数之和

    代码随想录算法训练营第七天|力扣454.四数相加II ,383. 赎金信,15. 三数之和,18. 四数之和 454.四数相加II 题目链接:四数相加II 参考:https://programmerc ...

  4. ACMNO.37 C语言-数字交换 输入10个整数,将其中最小的数与第一个数对换,然后把最大的数与最后一个数对换。写三个函数; ①输入10个数;②进行处理;③输出10个数。

    题目描述 输入10个整数,将其中最小的数与第一个数对换,然后把最大的数与最后一个数对换. 写三个函数: ①输入10个数:②进行处理:③输出10个数. 输入 10个整数 输出 整理后的十个数,每个数后跟 ...

  5. python两数相加代码_一边学编程,一边学语数外,用python编程全排列10以内两数加法...

    原标题:一边学编程,一边学语数外,用python编程全排列10以内两数加法 编程并不神秘 编程只是解决问题的一共方法 python是一门编程语言 python是一种解决问题的编程工具 在小学阶段,学习 ...

  6. 动态规划(0-1背包)--- 改变一组数的正负号使得它们的和为一给定数

    改变一组数的正负号使得它们的和为一给定数 494. Target Sum (Medium) Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5 Expl ...

  7. 信息学奥赛一本通 1245:不重复地输出数 | OpenJudge NOI 1.11 08:不重复地输出数

    [题目链接] ybt 1245:不重复地输出数 OpenJudge NOI 1.11 08:不重复地输出数 [题目考点] 1. 二分查找 2. 复杂度为O(nlogn)的排序 快速排序:时间复杂度O( ...

  8. 信息学奥赛一本通 1309:【例1.6】回文数(Noip1999) | 洛谷 P1015 [NOIP1999 普及组] 回文数

    [题目链接] ybt 1309:[例1.6]回文数(Noip1999) 洛谷 P1015 [NOIP1999 普及组] 回文数 注:两OJ上的问题考察内容相同,但输出要求不同 [题目考点] 1.高精度 ...

  9. python实现一个数如果恰好等于它的因子之和,这个数就称为“完数” 。例如, 6的因子为 1、2、3,而 6=1+2+3,因此 6 是完数。编程找出1000之内的所有完数

    一个数如果恰好等于它的因子之和,这个数就称为"完数" . 例如, 6 的因子为 1.2.3,同时6=1+2+3,因此 6 是完数. 编程找出 1000 之内的所有完数,并输出该完数 ...

最新文章

  1. 客快物流大数据项目(七):Docker总结
  2. 芯片组x299是服务器主板吗,最强的酷睿i9只能用它!X299主板首发评测
  3. Boost::context模块fiber的回声测试程序
  4. C#如何回到主线程,如何在委托指定线程执行
  5. 【Golang 快速入门】项目实战:即时通信系统
  6. Java实现二进制转换16进制(可以去掉空格)
  7. cassandra根据用户名密码登录cqlsh
  8. 编程基本功:代码都写不好,还写什么注释
  9. 通达信资金净流入公式_通达信主力净流入指标公式
  10. win10修改计算机密码,教你如何更改win10系统电脑密码
  11. Design Tradeoffs for SSD Performance
  12. lisp角度转换弪度_角度与弧度之间的换算(rad与度的换算)
  13. cpu爆了怎么排查和处理_怎么清理cpu,怎样可以证明cpu坏没坏-
  14. 项目管理软件怎么选?看看中国电信天翼云的选择
  15. java 中文转拼音_JAVA 将中文转化为拼音工具类
  16. 微信开发解决方案:(一)概述
  17. reverse()和reverse_copy()用法
  18. 从零搭建Spring Boot脚手架:开篇以及技术选型1
  19. 通俗理解STM32 SPI通信(主从双机SPI通信)
  20. 数学论文(优化方向)写作总结

热门文章

  1. CSS3详解:background
  2. sdk数值与android版本,Android SdkVersion的区别及获取版本信息方法
  3. grid++中打印表格时怎么让每页有打印表头_1分钟!学会快速打印标题行,轻松搞定不加班...
  4. vue移动端弹框组件
  5. HTML引入第三方类库项目需要授权解决方案
  6. windows下可用mysql吗_Windows下MySQL安装配置与使用
  7. CentOS7 下配置 Nginx + PHP7 + MariaDB + ThinkPHP5.1
  8. leetCode刷题(使用链表做加法)
  9. Java 原子类的操作 AtomicInteger
  10. 深藏不露,挖掘4种大脑网络中的管理工具