寻找丑数(Ugly Number)

题目:我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。

习惯上我们把1当做是第一个丑数。 求按从小到大的顺序的第1500个丑数。 

分析:这是一道在网络上广为流传的面试题,据说Google曾经采用过这道题。 


方法一:从小到大的遍历,这是我们最容易想到的方法了吧。不过这种方法很耗时间,用下面的代码查找第1500个丑数,Win7+VS2010用了33.067秒,效率很低。

#include <iostream>
#include <ctime>
using namespace std;

long findUglyNum(long willfindnum)
{
	long findednum = 0;
	long currentnum = 1, tmp;

	while(true)
	{
		tmp = currentnum;
		while(!(tmp%2))
			tmp /= 2;
		while(!(tmp%3))
			tmp /= 3;
		while(!(tmp%5))
			tmp /= 5;
		if(tmp == 1)
			++findednum;
		if(findednum == willfindnum)
			break;
		else 
			++currentnum;
	}
	return currentnum;
}


int main()
{
	time_t   begin,end;   
	begin=clock();    

	cout<<findUglyNum(1500)<<endl;

	end=clock();   
	cout<<"runtime:   "<<double(end-begin)/CLOCKS_PER_SEC<<endl;
	

	return 0;
}
该算法非常直观,代码也非常简洁,但最大的问题我们每个整数都需要计算。即使一个数字不是丑数,我们还是需要对它做求余数和除法操作。因此该算法的时间效率太差。


方法二:直接生成Ugly Numbers
接下来我们换一种思路来分析这个问题,试图只计算丑数,而不在非丑数的整数上花费时间。根据丑数的定义,丑数应该是另一个丑数乘以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三个数的最小者。前面我们分析的时候,提到把已有的每个丑数分别都乘以2、3和5,事实上是不需要的,因为已有的丑数是按顺序存在数组中的。对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于已有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会太大。我们只需要记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个T2。对乘以3和5而言,存在着同样的T3和T5。

int Min(int number1, int number2, int number3)
{
    int min = (number1 < number2) ? number1 : number2;
    min = (min < number3) ? min : number3;
    return min;
}
int findUglyNum2(int index)
{
	int *pUglyNumbers = new int[index];
	pUglyNumbers[0] = 1;
	int nextUglyIndex = 1;

	int *pMultiply2 = pUglyNumbers;
    int *pMultiply3 = pUglyNumbers;
    int *pMultiply5 = pUglyNumbers;

	while(nextUglyIndex < index)
	{
		int min = Min(*pMultiply2*2, *pMultiply3*3,*pMultiply5*5);
		pUglyNumbers[nextUglyIndex] = min;
		while(*pMultiply2*2 <= pUglyNumbers[nextUglyIndex])
			++pMultiply2;
		while(*pMultiply3*3 <= pUglyNumbers[nextUglyIndex])
			++pMultiply3;
		while(*pMultiply5*5 <= pUglyNumbers[nextUglyIndex])
			++pMultiply5;
		++nextUglyIndex;
	}
	int ugly = pUglyNumbers[nextUglyIndex - 1];
    delete[] pUglyNumbers;
    return ugly;
}


int main()
{
	time_t   begin2,end2;   	
	begin2=clock(); 
	cout<<findUglyNum2(1500)<<endl;
	end2=clock();   
	cout<<"runtime:   "<<double(end2-begin2)/CLOCKS_PER_SEC<<endl;
	

	return 0;
}
这种方法只要0.001秒,相比第一种方法的33.067秒就快的多了。当然第二种算法由于需要一个数组保存已经生成的丑数,需要额外的内存。第一种算法是没有这样的内存开销的。

运行结果:第1500个丑数:859,963,392, 第1690个丑数2,123,366,400,第1691个丑数int就越界了。
int表示的最大整数是2,147,483,647,由cout<<(std::numeric_limits<int>::max)()<<endl;可知。




方法三:使用STL  set

int main()
{
    long long  n=1500,cur;
	set<long long>s;
	s.insert(1);
	cur = *s.begin();
	while( --n ) {		
		s.insert( cur * 2 );
	    s.insert( cur * 3 );
		s.insert( cur * 5 );
		cur = *s.erase( s.begin() );
	}
	cout<<cur<<endl;
	return 0;
}

运行时间是0.046秒,只有5行代码。


方法四:这种方法没看懂,但是运行结果是正确的。注释掉check()速度还不错。来自http://www.iteye.com/topic/832545之方法五

#include <iostream>  
using namespace std;  
  
int nums5(int val)  
{  
    int n=0 ;   
    while (val >= 5)  
    {  
        n++ ;  
        val /= 5;  
    }  
    return n;  
}  
int nums35(int val)  
{  
    int n=0 ;  
    while (val >= 3)  
    {  
        n += 1+nums5(val);  
        val /= 3;  
    }  
    return n;  
}  
//基于因数分解求出val以内有多少个丑数(不包含1)   
int nums235(int val)  
{  
    int n=0 ;  
    while (val >= 2)  
    {  
        n += 1+nums35(val);  
        val /= 2 ;  
    }  
    return n;  
}  
//用二分法查找第n个丑数    
//对于X,如果X以内的丑数个数是n,而X-1以内的丑数个数是n-1,那么X就是第n个丑数    
int numOfIndex(int n)    
{    
    if(n == 1)    
        return 1;    
    n--;    
    int val1 = 1;    
    int nums1 = 0;    
    int val2 = 2;    
    int nums2 = nums235(val2); //nums2为val2的因数个数   
    while( nums2 < n )    
    {    
        val1 = val2;    
        nums1 = nums2;    
        val2 = val1*2;    
        nums2 = nums235(val2);    
    }    
    if( nums1 == n )  
        return val1;    
    if( nums2 == n )  
        return val2;    
  
    while(true)    
    {    
        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;    
        }    
    }    
}  
int check(int 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 )  
        cout << " v is not an ugly number! " << endl;  
    return val;    
}    
  
void calc(int n)    
{    
    long val = numOfIndex(n);    
    cout << n <<  " : " << val << endl;;    
    check(val);    
}    
  
int main(int argc ,char *argv[])  
{  
    int Number;  
    cout << "Please input a number : " ;  
    cin >> Number ;  
    calc(Number);   
    return 0;  
}




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值