题目描述
// 力扣
// 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求
// 按从小到大的顺序的第 n 个丑数。
// 牛客
// 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑
// 数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。
// 求按从小到大的顺序的第N个丑数。
题解
///
// 由题意可知,丑数n因子包括2 3 5,如果一个数能被2,3,5整除,那么连续将
// 其因子2,3,5除尽后(while (n%2 == 0): n = n/2...)得到的数为1。
// 这个数n就是丑数。我们大可以从1开始一个个判断每个整数是不是丑数
// 但是这样做效率太低了。
// 由于丑数因子相同,所以小丑数 × 2/3/5可以得到大丑数,这就可以用一个
// 数组存储丑数,利用动态规划使得我们从小丑数推断出大丑数。
// 注意要维持数组从小到大的性质。
// 力扣
// 执行用时: 2 ms, 在所有 Java 提交中击败了99.11% 的用户
// 内存消耗:37.3 MB, 在所有 Java 提交中击败了93.79%的用户
class Solution {
public int nthUglyNumber(int n) {
// for循环用于递归丑数数组的每一个位置,将该位置对应
// 的丑数从小到大推断出来。由于丑数只包含2 3 5这三个因子,
// 我们根据前面的小丑数,分别乘这三个因子就可以得到后面的大丑数,
// i2,i3,i5用于前面“小丑数”的索引,dp[i2]用于乘2,dp[i3]用于乘3,
// dp[i5]用于乘5,索引均初始化为0(数组第一位),
// 由于要分别乘3个因子,所以会得到3个丑数,又因为需要升序,
// 所以取乘出的三个丑数中最小的丑数作为当前遍历位置丑数即可。
// 此时参加推断的索引右移一位。
// 注意:再次强调,丑数的因子只有2,3,5,
// 我们设置i2,i3,i5这三个索引从0开始右移,
// 专门用于遍历索引乘2,3,5。也就是说所有的“小丑数”
// 都有机会被i2,i3,i5遍历并乘2 3 5中的任意一个,来推导下一个丑数
// 所以这个过程,就足以包含所有小丑数,推导出大丑数的所有情况(三种乘法)
int i2 = 0, i3 = 0, i5 = 0;
// 构建长度为n的数组,推断出的第n个元素(尾元素)就是我们要的元素
int[] dp = new int[n];
// 数组第一个丑数为1
dp[0] = 1;
for (int i = 1; i < n; i++) {
int b2 = dp[i2] * 2, b3 = dp[i3] * 3, b5 = dp[i5] * 5;
dp[i] = Math.min(b2, Math.min(b3, b5));
if (dp[i] == b2)
i2++;
if (dp[i] == b3)
i3++;
if (dp[i] == b5)
i5++;
}
return dp[n - 1];
}
}
// 牛客
// 运行时间:11ms,超过88.19%用Java提交的代码
// 占用内存:9584KB,超过71.92%用Java提交的代码
public class Solution {
public int GetUglyNumber_Solution(int index) {
if (index == 0)
return 0;
int[] dp = new int[index];
dp[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
for (int i = 1; i < index; i++) {
int b2 = dp[i2] * 2, b3 = dp[i3] * 3, b5 = dp[i5] * 5;
dp[i] = Math.min(b2, Math.min(b3, b5));
if (dp[i] == b2)
i2++;
if (dp[i] == b3)
i3++;
if (dp[i] == b5)
i5++;
}
return dp[index - 1];
}
}