丑数-动态规划

题目连接:https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey.


基本思想

用于一个数组dp[]保存丑数,从小到大保存
根据丑数的定义,丑数可以写成2x*3y*5^z
因此可以把前面已有的丑数乘以2、3、5生成新的丑数,放进dp数组中就可以了
现在要考虑的时,dp数组中的丑数是要从小到大排列的,如何实现
一种最直接的方式就是,每次对已有的dp数组中的每个元素都乘以2、3、5,然后从中选出最小的排在dp后面即可
但是,这种方法会导致大量元素被重复乘以2、3、5
有什么办法可以较少不必要的重复计算呢?
我们可以这样想一下,对于一个已有的dp数组,假设它的最后一个数是N(目前已知的最大的丑数)
我们要求大于N的最小的丑数,即下一个丑数

  1. 先让dp数组中所有元素乘以2,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N2.假设从位置p2开始,dp[p2…i-1]中的丑数2>N. N2=2dp[p2]
  2. 让dp数组中所有元素乘以3,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N3.假设从位置p3开始,dp[p3…i-1]中的丑数3>N. N3=2dp[p3]
  3. 让dp数组中所有元素乘以5,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N5.假设从位置p5开始,dp[p5…i-1]中的丑数2>N. N5=2dp[p5]
  4. 根据N2,N3,N5,求出dp的下一个丑数dp[i]=min(N2,N3,N5);
    我们求出下一个丑数dp[index]后,需要更新标记位p2,p3,p5可,即如果dp[p2]*2=dp[i],则p2++。如果dp[p3]2=dp[i],则p3++。如果dp[p5]2=dp[i],则p5++。
    p2,p3,p5其实是将原始dp数组截成了两段,dp[0…p2]前面的数
    2均<dp[last],dp[p2…]后面的数
    2均大于dp[last].同理p3,p5是对应乘以3,5的截断位置。

代码

class Solution {
public:
	//用于一个数组dp[]保存丑数,从小到大保存
	//根据丑数的定义,丑数可以写成2^x*3^y*5^z
	//因此可以把前面已有的丑数乘以2、3、5生成新的丑数,放进dp数组中就可以了
	//现在要考虑的时,dp数组中的丑数是要从小到大排列的,如何实现
	//一种最直接的方式就是,每次对已有的dp数组中的每个元素都乘以2、3、5,然后从中选出最小的排在dp后面即可
	//但是,这种方法会导致大量元素被重复乘以2、3、5
	//有什么办法可以较少不必要的重复计算呢?
	//我们可以这样想一下,对于一个已有的dp数组,假设它的最后一个数是N(目前已知的最大的丑数)
	//我们要求大于N的最小的丑数,即下一个丑数
    int GetUglyNumber_Solution(int index) {
    	if(!index) return 0;
    	int dp[index]; 
    	int p2=0,p3=0,p5=0,i=1;
    	dp[0]=1;
    	while(i<index){
    		//dp[i]=min(N2,N3,N5);
    		dp[i]=min(min(dp[p2]*2,dp[p3]*3),dp[p5]*5);
    		//更新标记位 
			if(dp[i]==dp[p2]*2) p2++;
			if(dp[i]==dp[p3]*3) p3++;
			if(dp[i]==dp[p5]*5) p5++;
    		i++;
		}
		for(i=0;i<index;i++) cout<<dp[i]<<",";
		cout<<endl;
    	return dp[index-1];
    }
};

总结

代码很简单,但是一开始没有想到用求好的丑数去乘以2、3、5来推出下一个丑数!在用已有的丑数求下一个丑数时,为了避免大量的重复计算,分别使用标记位p2,p3,p5来将原始的丑数数组截取成两半,p2位置前的丑数2都比当前丑数数组dp的最后一个元素小,p2位置后的丑数2都比dp数组的最后一个元素大;同理:p3位置前的丑数3都比当前丑数数组dp的最后一个元素小,p3位置后的丑数3都比dp数组的最后一个元素大;p5位置前的丑数2都比当前丑数数组dp的最后一个元素小,p5位置后的丑数2都比dp数组的最后一个元素大。这样标记的好处就是,在求下一个丑数时,我们没有必要对原始丑数数组dp中的所有原始都进行乘以2、3、5的操作,因为根据标记位,标记为前半段的乘以相应的2(3,5)都不可能是下一个丑数(它们均小于当前已经求出的丑数数组的最后一个丑数(最大)),而月往标记位后面走,乘积指挥越来越大。因此我们只需要在标记位处乘以2,3,5,再求这3个结果的最小值,即为下一个丑数。注意,求出新的丑数后,需要更新标记位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值