题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
方法一
首先想到的是如果一个数是丑数,那么它应该可以分解成一个丑数与2或3或5的积,所以可以将已经得到的丑数进行存储,如果i满足下述的条件,则i是丑数:
- i能够整除2且i/2也是丑数
- i能够整除3且i/3也是丑数
- i能够整除5且i/5也是丑数
所以采用vector<int> dp,如果dp[i]==1表示i是丑数,否则表示i不是丑数。
代码如下:
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if( index <= 6 )
return index;
vector<int> dp;
for( int i=0;i<=6;i++ )
dp.push_back(1);
int count = 6;
int cur = 7;
while( count < index )
{
if( ( cur%5==0 && dp[cur/5]==1 ) || ( cur%3==0 && dp[cur/3]==1 ) || ( cur%2==0 && dp[cur/2]==1 ) )
{
dp.push_back(1);
count++;
}
else
dp.push_back(0);
cur++;
}
return cur-1;
}
};
缺点:空间复杂度过大,第1500个丑数的大小为859963392,就需要859963392这么大的空间,开销太大
方法二
因为方法一提交后会报出内存超过限制的错误,所以改用了unordered_set<int> s来存储已知的丑数,用s.find(i)来判断i是否已经存储在s内从而判断i是否为丑数,减少了空间复杂度,代码如下:
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if( index <= 6 )
return index;
unordered_set<int> s;
for( int i=0;i<=6;i++ )
s.insert(i);
int count = 6;
int cur = 7;
while( count < index )
{
if( ( cur%2==0 && s.find(cur/2)!=s.end() ) || ( cur%3==0 && s.find(cur/3)!=s.end() ) || ( cur%5==0 && s.find(cur/5)!=s.end() ) )
{
s.insert(cur);
count++;
}
cur++;
}
return cur-1;
}
};
这个方法虽然把空间复杂度降下来了,但是时间复杂度更高了,所以会超时
方法三
根据上面说的,一个丑数应该是由另外一个更小的丑数乘以2或3或5得到的,那么可以换种思路,如果已知一部分丑数,其中最大的丑数M,如何找到M的下一个丑数呢?
因为一个丑数应该是由另外一个更小的丑数乘以2或3或5得到的,所以可以对已知的丑数(排好序的)全部乘以2、3、5,选其中最小的一个,就是M的下一个丑数。因为需要找到的是大于M的最小的丑数,所以在对已知的丑数乘以2、3、5的过程中,不需要所有的已知的丑数都求一遍,只要的到大于M的数,更大的丑数就不用去乘了。
此外,还有一个优化就是,每次对已知的丑数乘以2、3、5的过程,也不需要从头开始,只要从上次搜索的结束位置开始即可。
举例:现在已知的排好序的丑数为:1 2 3 4 5 6,那么已知的最大丑数M=6。然后将已知的丑数分别与2、3、5相乘:
- 与2相乘得到2 4 6 8,此时8已经大于M,后面的数就可以不再计算,记录下min2=8,以及此时与2相乘的4在序列中的位置i2=3;
- 与3相乘得到3 6 9,此时9已经大于M,后面的数就可以不再计算,记录下min3=9,以及此时与3相乘的3在序列中的位置i3=2;
- 与5相乘得到5 10,此时10已经大于M,后面的数就可以不再计算,记录下min5=10,以及此时与5相乘的2在序列中的位置i5=1;
那么,M的下一个值应该为min2,min3和min5中的最小值,即8,将8加入到序列中。
继续搜索8的下一个丑数时,与2相乘的值可以从i2+1的位置开始,而与3相乘的值则应该从i3开始,与5相乘的值应该从i5位置开始。
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if( index <= 6 )
return index;
vector<int> res(index+1, 0);
for( int i=1;i<=6;i++ )
res[i] = i;
int i1=1, i2=1, i3=1;//用于记录上一次的位置
for( int i=7;i<=index;i++ )
{
//每个数乘以2
int min2 = 0; //记录乘以2得到的第一个大于M的数
while( min2 <= res[i-1] )
{
min2 = res[i1]*2;
i1++;
}
//每个数乘以3
int min3 = 0;//记录乘以3得到的第一个大于M的数
while( min3 <= res[i-1] )
{
min3 = res[i2]*3;
i2++;
}
//每个数乘以5
int min5 = 0;//记录乘以5得到的第一个大于M的数
while( min5 <= res[i-1] )
{
min5 = res[i3]*5;
i3++;
}
res[i] = min( min2, min(min3,min5) );
if( res[i] == min2 )
{
i2--;
i3--;
}
if( res[i] == min3 )
{
i1--;
i3--;
}
if( res[i] == min5 )
{
i1--;
i2--;
}
}
return res[index];
}
};
这样时间复杂度和空间复杂度就都可以满足要求了。