剑指 Offer 49. 丑数
题目描述
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10 输出: 12 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 说明:
-
1 是丑数。
-
n 不超过1690。
作者:Krahets 链接:力扣 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路
-
丑数的递推性质: 丑数只包含因子 2,3,5 ,因此有 “丑数 = 某较小丑数 ×某因子” (例如:10=5×2)。
设已知长度为 n 的丑数序列x1,x2......,xn ,求第 n+1 个丑数 x[n+1]。根根据递推性质,丑数 x [n+1] 只可能是以下三种情况其中之一(索引 a,b,c 为未知数):
丑数递推公式: 若索引 a,b,c 满足以上条件,则下个丑数 n+1为以下三种情况中的 最小值 ;
x**n+1=min(x**a×2,x**b×3,x**c×5) 由于x[n+1] 是 最接近 x [n]的丑数,因此索引 a,b,c 需满足以下条件:
因此,可设置指针a,b,c 指向首个丑数(即 11 ),循环根据递推公式得到下个丑数,并每轮将对应指针执行 +1+1 即可。
动态规划解析:
-
状态定义: 设动态规划列表 dp ,dp[i] 代表第 i+1 个丑数;
-
转移方程:
-
当索引 a,b,c 满足以下条件时, dp[i] 为三种情况的最小值;
-
每轮计算 dp[i] 后,需要更新索引 a,b,c 的值,使其始终满足方程条件。实现方法:分别独立判断 dp[i] 和 dp[a]×2 , dp[b]×3 ,dp[c]×5 的大小关系,若相等则将对应索引 a , b , c 加 1 ;
-
初始状态: dp[0]=1 ,即第一个丑数为 1 ;
-
返回值: dp[n−1] ,即返回第 n 个丑数;
代码如下:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Solution {
public:
int nthUglyNumber(int n) {
int a=0,b=0,c=0;
vector<int> dp(n);
dp[0]=1; // 初始值为1
for(int i=1;i<n;i++){
// 分别计算乘2,乘3和乘5的最小丑数
int n2=dp[a]*2,n3=dp[b]*3,n5=dp[c]*5;
dp[i]=min(min(n2,n3),n5); // 取三者中的最小值作为新的丑数
if(dp[i]==n2) a++; // 如果新的丑数是由a指针所指向的数乘2得到的,那么a指针向前移动一位
if(dp[i]==n3) b++; // 如果新的丑数是由b指针所指向的数乘3得到的,那么b指针向前移动一位
if(dp[i]==n5) c++; // 如果新的丑数是由c指针所指向的数乘5得到的,那么c指针向前移动一位
}
return dp[n-1]; // 返回第n个丑数的值
}
};
int main(){
Solution so;
int n;
cin>>n;
cout<<so.nthUglyNumber(n);
return 0;
}
复杂度分析
-
时间复杂度 O(N) : 其中 N=n ,动态规划需遍历计算 dp 列表。
-
空间复杂度 O(N) : 长度为 N 的 dp 列表使用 O(N) 的额外空间。