• 剑指Offer——面试题34:丑数


    题目:我们把只包含因子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只是一个未经验证随意编撰的数字。

  • 相关阅读:
    webpack 关于跨域的配置
    如何使用css变量
    样式重置
    vue+element_ui上传文件,并传递额外参数(自动上传)
    LeetCode-46-全排列
    LeetCode-39-组合总数
    LeetCode-33-搜索旋转排序数组
    LeetCode-207-课程表
    LeetCode-15-三数之和
    LeetCode-盛最多水的容器
  • 原文地址:https://www.cnblogs.com/superpig0501/p/4072617.html
Copyright © 2020-2023  润新知