264. 丑数 II
编写一个程序,找出第 n 个丑数。
丑数就是质因数只包含 2, 3, 5 的正整数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
题解:
方法一:暴力法
- 即利用丑数的性质当作“判断“”来解题,因此我们只需要写下如何验证一个数是否是丑数的代码,再加以数组储存每次的丑数,当丑数的数量达到所求时再输出最后一个丑数即可;
- 不过此法需要对从2开始的每一个数都进行验证,复杂度极高,且运行会超时。
代码:
int nthUglyNumber(int n){
int temp = 2;//用来储存最后一个丑数
int sum = 1;
while(sum!=n)//丑数不到n个就一直循环
{
int num = temp;//需要个中间量来完成丑数的验证过程
while(num!=1)
{
if(num%2==0)
{
num=num/2;
continue;
}
else if(num%3==0)
{
num=num/3;
continue;
}
else if(num%5==0)
{
num=num/5;
continue;
}
else
{
goto A;
}
}
sum++;
A:temp++;
}
return temp-1;
}
方法二:三指针+动态规划
- 此法比较考验逻辑思维以及反推的能力。因为我们的目标是求出第n个丑数,既然如此我们在寻找的时候可不可以不像方法一那样每一个数都找一遍,而是直接从丑数里一个一个找呢?这时我们可以发现可以省去验证此数是否是丑数的那段代码了,因为我们不是要去验证一个数是否是丑数。
依次我们会思考,如何使得我们找的每个数都是丑数?
再次读题发现丑数的性质不过就是质因数为2,3,5
而已。
- 那么对于最开始给定的丑数1,我们给他乘任意个2,任意个3,任意个5之后,他依然还是丑数;同样对于进阶后的丑数2,3,5,我们给他乘任意个2,任意个3,任意个5之后,他依然也还是丑数…
- 因此我们发现我们只需要调整一下寻找数字时两数之间的跨度即可。
- 方法一中我们是每一个数字都寻找了,因此我们的跨度是1,我们这里只需要修改跨度即可。
- 但是要怎样我们才不会漏算丑数呢?如果我们直接漫无边际的去找,看到一个是一个,那么会混乱我们的思维,因此我们需要制定一个寻找的准则:即从小到大寻找。
我们这里采用三指针+动态规划来完成上述的描绘过程:
- 首先由于上述可知,我们算下一个丑数时为了减少计算量,是需要前面的丑数的大小数据的,即直接在前面丑数的基础上乘对应的2,3,5可以更快达到自己想得到的丑数。因此我们可以创建一个数组来保存你找到的丑数,且从小到大排列。
- 接着设立三个指针,它们分别对应乘的2,3,5的个数(代码中对应的是个数-1),接着进入循环,即不找到n个丑数不出去的循环。
- 为了满足从小到大寻找丑数,我们创建一个least函数找出在丑数1的基准上乘若干个2,3,5之后形成的最小的丑数,找到后把他放进数组内,接着改变一下他对应指针的指向位置(即它们乘的2,3,5个数),然后进入下次循环。
- 因为动态规划本身就是一个所谓“后”=“前”或“前”=“后”的过程,所以由于指针改变了,下一次用least比较时,参与比较的三个数它们的基准也发生改变了。
需要注意的是,之所以 dp[i] = least(dp[a1]*2,dp[a2]*3,dp[a3]*5);,其实是对dp[i]前面的每一个丑数都需要乘2,乘3,乘5试一试,看是否乘过之后能否构成新的丑数,但是由于我们对于a1,a2,a3,即三个指针已经做了记忆化处理(即次数每次累加),所以这里看似只进行了一次乘法处理,不过是DP的递推过程隐藏了
leetcode上有一句话对上述红字过程描述的比较透彻,这里贴上:
Java代码
class Solution {
public int nthUglyNumber(int n) {
int a1 = 0;
int a2 = 0;
int a3 = 0;
int[] dp = new int[n];
dp[0] = 1;
for(int i=1;i<n;i++){
dp[i] = least(dp[a1]*2,dp[a2]*3,dp[a3]*5);
if(dp[i]==dp[a1]*2){
a1++;
}
if(dp[i]==dp[a2]*3){
a2++;
}
if(dp[i]==dp[a3]*5){
a3++;
}
}
return dp[n-1];
}
public int least(int a,int b,int c){
return Math.min(Math.min(a,b),c);
}
}
C代码:
//新思路:三指针法,第一个丑数设为1,接着不断用235乘以它们指针所在的位置,比较谁小的方法,如果采用某一指针,则指针进一;
int least(int x,int y,int z)
{
int temp = x<y?x:y;
return temp<z?temp:z;
}
int nthUglyNumber(int n){
int*nums=(int*)malloc(sizeof(int)*n);
nums[0]=1;
int p = 0;
int q = 0;
int m = 0;
for(int i=1;i<n;i++)
{
nums[i]=least(nums[p]*2,nums[q]*3,nums[m]*5);//这里其实就是我们日常遇到的动态规划的过程,即“后”=“前”
if(nums[i]==nums[p]*2)
{
p++;
}
if(nums[i]==nums[q]*3)
{
q++;
}
if(nums[i]==nums[m]*5)
{
m++;
}
}
return nums[n-1];
}