题目:我们把只包含因子2、3和5的数称为丑数(Ugly Number)。求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做第一个丑数。
思路:和书上描述的第二个思路一致。首先建立一个大小为N(此问题中,N为1500)的int数组,然后每次求出比当前已知最大丑数M大,且与当前丑数相邻的丑数(所谓相邻,就是指两个丑数之间不包含其他丑数),再把新的丑数放入数组。直到数组满,然后取出数组尾部的最后一个数,即为第N个丑数。
关键问题在于如何求出比当前已知最大丑数大,且与当前丑数相邻的丑数。书上给出的思路是这样的,维护三个指针P2、P3和P5。对于第一个指针P2,指向乘2后恰比M大的位置,并且把这个恰比M大的数字记为M2。使用相应的规则,维护P3、P5的位置,并记录M3、M5。之后比较M2、M3与M5,选择最小的,填入数组中M之后的位置,并把M更新为最新填入的这个数字。
下面讨论一下时间复杂度。首先对于三个指针P2、P3和P5,每次移动时都是从之前的位置开始移动,因此移动的距离只是常数级。各类比较次数也是常数级。主要的时间消耗在于填满整个数组。所以时间复杂度是O(N)。空间复杂度也同样是O(N),同样地,主要的开销就是这个数组。
下面是我的代码,用C++实现。测试也写在一起了。
#include <iostream.h> int min(const int a, const int b, const int c){ return a < b ? (a < c ? a : c):(b < c ? b : c); } int GetUglyNumber(const int index){ if(index <= 0) return 0; int * map = new int[index]; int i = 0; map[i] = 1; int p2 = 0, p3 = 0, p5 = 0; while(i < index - 1){ while (map[p2++] * 2 <= map[i]); //Find the next index of P2 while (map[p3++] * 3 <= map[i]); while (map[p5++] * 5 <= map[i]); map[++i] = min(map[--p2] * 2, map[--p3] * 3, map[--p5] * 5); //Watch out when decide whether the ++/-- opeator is a prefix or a suffix. } int ugly = map[i]; delete[] map; //Remember to release the memory. return ugly; } int main() { cout << GetUglyNumber(1500) << endl; return 0; }
输出的结果是:859963392
More discussion:
1、这个数组占用的空间是6KB,书上也提到了。这是求第1500个丑数的情况。如果求排在更后面的丑数呢?事实是,当你用这个程序求第1691个丑数的时候,还会有输出,这个值是2125764000。但是求第1692个丑数的时候,就会出现程序崩溃(VC6)。我的猜测是,主要问题在于int越界了。
2、开始考虑空间复杂度这个问题的时候,我还在想,如果用bit-map来表示每一个数字,把bit-map初始化为全0,之后如果一个数是丑数,则把相应的比特位置为1。但是随便列一下30之内的丑数,你马上就会发现,丑数在自然数中的分布,是随着自然数的变大而逐渐变稀疏的(稀疏这个词,是借鉴质数在自然数中的分布得来的)。1-10中,只有7不是丑数,11-20中,就只剩下12,15,16,18,20五个丑数了,之后的情况不再详述。现在的问题就是,如果使用int数组存储每一个丑数,那么一个int占32个比特位;如果使用bit-map方式标记丑数,第1500个丑数和第1499个丑数之间的差,是否小于32。如果小于32,那么用bit-map方式是值得的,因为空位很少;但如果大于32,那肯定是得不尝试的。现实很无情,结果也很惨,GetUglyNumber(1499)返回的值是854296875,和上面提到的GetUglyNumber(1500)的输出结果——859963392——一比,竟然有百万数量级的差值。虽然丑数不是均匀分布的,但是这个数量级的差距,直接宣布了bit-map方式的死刑。
3、综上所述,老老实实在堆区的开数组其实是最实在的方法。当int不够用的时候,那就上int64吧。比如要求第5000个丑数,那么使用int64数组,内存开销是8bytes * 5000 = 40KB,说到底也没有多大。不过int64对应的丑数极限在哪,我也不知道,5000只是一个未经验证随意编撰的数字。