面试题 <丑数>(10)

这篇博客探讨了如何优化求解丑数问题的算法,通过创建有序丑数数组并利用乘以2, 3, 5的方式生成下一个丑数,避免在非丑数上浪费时间。文中介绍了关键在于保持数组排序,并且只关注第一个大于当前最大丑数的结果,以此提高效率。" 135947185,20011709,金和OA-jc6-uploadfileblock 任意文件上传漏洞详解,"['web安全', '漏洞复现']
摘要由CSDN通过智能技术生成

 # 面试题: 丑数



/*

题目: 我们把只包含因子 2,3和5的数称为丑数。求按从小到大的顺序的第1500个丑数,

例如 6,8 都是丑数,但是14 不是,因为它包含因子 7. 习惯上我们把1 当作第一个丑数。


*/


# 题目分析:

// 所谓丑数就是能连续被 2,3,5 整除最后得到1 的整数;

//比如 整数6是丑数,先将6连续除2,商3,如果余数为0,则继续将3除3,商1,余数为0,

//继续将1除5,商0,余1; 则6是丑数;
 

# 基于上面的思想我们可以下出下面的代码<为代码效果明显起见,都已找第1500个丑数为例>:


#include<stdio.h>
#include<stdlib.h>
#include<windows.h>

int Number_Chou(int num)
{
	while(num%2 == 0)
		num /= 2;
	while(num%3 == 0)
		num /= 3;
	while(num%5 == 0)
		num /= 5;

	return (num == 1) ? 1:0;//最后商为1则返回;
}

int Search_Chou(int num)
{
	int count = 0;
	int steps = 0;

	if(num <= 0)
		return 0;
	while(count < num)
	{
		++steps;//当前操作数
		if(Number_Chou(steps))
		    count++;//丑数个数
	}

	return steps;
}

int main()
{
	int start = GetTickCount();//计时函数
	int end = 0;
	int num = 1500;
	
	int ret = Search_Chou(num);
	
	end = GetTickCount();

	printf("所用时间:%d秒\n\n所求丑数为:%d\n\n",(end - start)/1000,ret);

	system("pause");
	return 0;
}

@ 注意: 当你运行上面的代码的时候,你会发现等待结果出来的话,会需要几十秒,效率非常低;

# 运行结果



#  <空间换取时间> 换种思路想想,这种效率很低的方法肯定不是我们所想要的;

如前所述,我们发现采用遍历法求第K个丑数的效率十分低下,我们在前面求第1500个丑数花去了33秒的时间,这还

是在我I7 3770K的电脑上运行的。所以我们考虑有没有一种更加高效的方法。在面试题9:斐波那契数列中我们使用

了一种“用空间还时间”的方法来提高求斐波那契数列的速度。这种编程思想也可以应用在这道题目当中,我们为所有求出的丑数创建数组,不在非丑数上面浪费时间。

根据丑数的定义,我们可以知道丑数可以由另外一个丑数乘以2,3或者5得到。因此我们创建一个数组,里面的数字

排好序的丑数,每一个丑数都是前面的丑数乘以2,3或者5得到的。这种思路的关键在于怎样确保数组里面的数字

是排序的。

假设丑数数组中已经有若干个排好序的丑数,比如1,2,3,4,5。我们把当前丑数数组中的最大数记为M,这里

M=5。我们接下来分析如何生成下一个丑数。根据前面的介绍,我们知道这个丑数肯定是前面丑数数组中的数字乘以

2,3,5得到的。所以我们首先考虑把已有的每个丑数乘以2,在乘以2的时候,能够得到若干个小于或者等于M的结

果。由于是按照顺序生成的,小于或者等于M的数肯定已经在丑数数组当中了,我们不需要再次考虑;当然还会得到

若干大于M的结果,但是我们只需要第一个大于M的结果,因为我们希望丑数是按顺序排列的,所以其他更大的结果

可以以后考虑。我们把得到的第一个乘以2以后得到的大于M的结果记为M2。同样,我们把已有的每一个丑数乘以3和

5,能得到第一个大于M的结果M3和M5。那么M后面的那一个丑数应该是M2,M3和M5当中的最小值:Min

(M2,M3,M5)。比如将丑数数组中的数字按从小到大乘以2,直到得到第一个大于M的数为止,那么应该是2*2=4<M,

3*2=6>M,所以M2=6。同理,M3=6,M5=10。所以下一个丑数应该是6。


前面分析的时候,提到把已有的每个丑数分别都乘以2,3和5。事实上这不是必须的,因为已有的丑数是按顺序存放

在数组中的,对乘以2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的结果都会小于等于(<=)已

有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会大于已有的最大丑数。因此我们只需要记下这个丑数的

置,同时每次生成新的丑数的时候去更新这个T2。对于乘以3和5,同样存在这样的T3和T5。


#基于上面的思路我们有可以写下面的代码:


#include<stdio.h>
#include<stdlib.h>

int Min(int num1,int num2,int num3)
{
	int min = (num1 < num2) ? num1 : num2;

	min = (min < num3) ? min : num3;

	return min;
}

int Search_Chou(int num)
{
	int ret = 0;
	int change = 1;
	int min = 0;
	int *ob2 = NULL;
	int *ob3 = NULL;
	int *ob5 = NULL;

	int *ptr = (int *)malloc(num*sizeof(int));
	if(ptr == NULL)
		printf("out of memery\n");

	if(num<0)
		return 0;

	ptr[0] = 1;
	ob2 = ptr;
	ob3 = ptr;
	ob5 = ptr;

	while(change < num)
	{
		min = Min((*ob2)*2,(*ob3)*3,(*ob5)*5);
		ptr[change] = min;//最小的放进去

		while((*ob2)*2 <= ptr[change])//注意这里都是小于等于
			++ob2;//记录当前位置
		while((*ob3)*3 <= ptr[change])
			++ob3;//记录当前位置
		while((*ob5)*5 <=ptr[change])
			++ob5;//记录当前位置
		++change;
	}

	ret = ptr[change - 1];//注意返回值的下标;

	free(ptr);//记得释放
	ptr = NULL;
	return ret;
}

int main()
{

	int num = 1500;
	int ret = 0;

	ret = Search_Chou(num);

	printf("所求丑数为:%d\n",ret);

	system("pause");
	return 0;
}
# 运行结果

#结果瞬间出来,效率可见一斑; 


# 基于上述思路,在下面列出一种便于理解的代码,与上述思路一样,作为参考;


int Search_Chou()
{
	  int num[]=new int[1501]; 
    num[0] = 0; 
    num[1] = 1; 
    int two = 0,three = 0,five = 0;//刚好大于现有最大丑数的三个 

    for(int i = 1; i <= 1499; i++)
	{//每次找出一个丑数 
		for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的两倍刚好大于上回找出的丑数,将它值记录下来 
    if((2*num[j-1] <= num[i])&&(2*num[j] > num[i])) two=num[j]*2; 
       for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的三倍刚好大于上回找出的丑数,将它值记录下来 
    if((3*num[j-1] <= num[i])&&(3*num[j] > num[i])) three=num[j]*3; 
       for(int j = 1; j <= i ; j++)//从头向后扫描,若某数的五倍刚好大于上回找出的丑数,将它值记录下来 
    if((5*num[j-1] <= num[i])&&(5*num[j] > num[i])) five=num[j]*5; 
    //比较出三个数中最小的 
	   if(two>=three&&five>=three) num[i+1] = three; 
       else if(two>=five&&three>=five) num[i+1] = five; 
       else if(five>=two&&three>=two) num[i+1] = two; 
 } 
	return num[i+1];
}


@ 关于丑数这个神奇的概念就说到这里,在这里给大家推荐一本书《编程之美》,里面的解体思路都是由简入深,非

常适合培养我们的解题思路!


Ps: 爱拼才会赢!


Thanks!



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值