• 剑指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只是一个未经验证随意编撰的数字。

  • 相关阅读:
    input 只能输入数字
    “学生宿舍管理系统”主要内容及特点
    web_03Java ee实现定时跳转,使用C3P0,DBUtils类重构数据库操作
    DBUtils工具类的使用
    C3P0连接池
    java ee 中 Jsp 页面的定时的跳转(数字倒数)
    JSP中实现网页访问统计的方法【转】
    Java web验证码
    web_02Java ee实现验证码,网站访问次数功能
    web_01Java ee实现登陆注册功能
  • 原文地址:https://www.cnblogs.com/superpig0501/p/4072617.html
Copyright © 2020-2023  润新知