寻找丑数的问题
无意间看到一个有关丑数的问题,问题描述:我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第n个丑数。
最简单的方法就是从小到大遍历一遍,找出第n个,但是耗时实在是无法忍受:
#include<stdio.h> int IsUgly(int checkNum) { while(checkNum % 2 == 0) checkNum /= 2; while(checkNum % 3 == 0) checkNum /= 3; while(checkNum % 5 == 0) checkNum /= 5; if(checkNum == 1) return 1; else return 0; } int main() { int uglyNum; printf("input the number:\n"); scanf("%d",&uglyNum); int numCounter=0; int uglyCounter=0; if(uglyNum <= 0) printf("illegal input!\n"); else { while(uglyCounter < uglyNum) { numCounter++; if(IsUgly(numCounter) == 1) uglyCounter++; }
printf("output the result:\n"); printf("%d\n",numCounter);
}
return 0;
}
于是乎,有了第二种方法,就是从第一个丑数开始,乘以2,3,5,把每个丑数都通过乘法算出来,然后一直下去,直到得到第n个丑数,http://www.cnblogs.com/mingzi/archive/2009/08/04/1538491.html 里的算法描述比较清晰,可以参考下,这里贴出关键部分:
我们假设数组中已经有若干个丑数,排好序后存在数组中。我们把现有的最大丑数记做M。现在我们来生成下一个丑数,该丑数肯定是前面某一个丑数乘以2、3或者5的结果。我们首先考虑把已有的每个丑数乘以2。在乘以2的时候,能得到若干个结果小于或等于M的。由于我们是按照顺序生成的,小于或者等于M肯定已经在数组中了,我们不需再次考虑;我们还会得到若干个大于M的结果,但我们只需要第一个大于M的结果,因为我们希望丑数是按从小到大顺序生成的,其他更大的结果我们以后再说。我们把得到的第一个乘以2后大于M的结果,记为M2。同样我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么下一个丑数应该是M2、M3和M5三个数的最小者。
前面我们分析的时候,提到把已有的每个丑数分别都乘以2、3和5,事实上是不需要的,因为已有的丑数是按顺序存在数组中的。对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于已有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会太大。我们只需要记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个T2。对乘以3和5而言,存在着同样的T3和T5。
#include<stdio.h> int Min(int num1, int num2, int num3) { int temp = num1 < num2 ? num1 : num2; return (temp < num3 ? temp : num3); } int main() { int uglyIndex; printf("input the number:\n"); scanf("%d",&uglyIndex); if(uglyIndex <= 0) printf("illegal input!\n"); else { int *uglyNums = new int[uglyIndex]; uglyNums[0]=1; int uglyCounter=1; int T2 = 0; int T3 = 0; int T5 = 0; while(uglyCounter < uglyIndex) { int nextUgly = Min(uglyNums[T2]*2, uglyNums[T3]*3, uglyNums[T5]*5); uglyNums[uglyCounter++]=nextUgly; if(nextUgly == uglyNums[T2]*2) T2++; if(nextUgly == uglyNums[T3]*3) T3++; if(nextUgly == uglyNums[T5]*5) T5++; } printf("output the result:\n"); printf("%d\n",uglyNums[uglyIndex-1]); } return 0; }
后来搜了下,发现了一个好贴 http://www.iteye.com/topic/832545 里面总结了五种方法,其中有个因数分解的方法,挺有意思的,就直接贴过来好了:
public class Q64 { //基于因数分解求出val以内有多少个丑数(不包含1) static int nums235(long val) { int n = 0; while(val>=2) { n += 1+nums35(val); val/=2; } return n; } static int nums35(long val) { int n = 0; while(val>=3) { n += 1+nums5(val); val/=3; } return n; } static int nums5(long val) { int n = 0; while(val>=5) { n++; val/=5; } return n; } //用二分法查找第n个丑数 //对于X,如果X以内的丑数个数是n,而X-1以内的丑数个数是n-1,那么X就是第n个丑数 static long numOfIndex(int n) { if(n == 1) return 1; n--; long val1 = 1; int nums1 = 0; long 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; for(;;) { 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; } } } static long check(long 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) System.out.println(val + " 不是丑数"); return val; } static void calc(int n) { long val = numOfIndex(n); System.out.println(n + ":" + val); check(val); } static void calc1(int n) { calc(n-1); calc(n); calc(n+1); } public static void main(String[] args) { long t = System.currentTimeMillis(); calc1(100); calc1(1000); calc1(1500); calc1(5000); calc1(9918); calc1(10000); System.out.println("used time " + (System.currentTimeMillis()-t) + " ms."); /* check(257363915118311232L); check(257492065429687488L); check(257492065429687520L);*/ } }
大家都去原帖膜拜吧~