基本思想
用于一个数组dp[]保存丑数,从小到大保存
根据丑数的定义,丑数可以写成2x*3y*5^z
因此可以把前面已有的丑数乘以2、3、5生成新的丑数,放进dp数组中就可以了
现在要考虑的时,dp数组中的丑数是要从小到大排列的,如何实现
一种最直接的方式就是,每次对已有的dp数组中的每个元素都乘以2、3、5,然后从中选出最小的排在dp后面即可
但是,这种方法会导致大量元素被重复乘以2、3、5
有什么办法可以较少不必要的重复计算呢?
我们可以这样想一下,对于一个已有的dp数组,假设它的最后一个数是N(目前已知的最大的丑数)
我们要求大于N的最小的丑数,即下一个丑数
- 先让dp数组中所有元素乘以2,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N2.假设从位置p2开始,dp[p2…i-1]中的丑数2>N. N2=2dp[p2]
- 让dp数组中所有元素乘以3,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N3.假设从位置p3开始,dp[p3…i-1]中的丑数3>N. N3=2dp[p3]
- 让dp数组中所有元素乘以5,会得到一部分小于N的结果和一部分大于N的结果,小于N的结果肯定已经早就保存在dp中了,我们需要从大于N的结果中拿出最小的N5.假设从位置p5开始,dp[p5…i-1]中的丑数2>N. N5=2dp[p5]
- 根据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个结果的最小值,即为下一个丑数。注意,求出新的丑数后,需要更新标记位。