[230522 剑指49] 丑数
一 题目
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1
是丑数。n
不超过1690。
二 整体思路
**(确定解法)从后往前看:**因为丑数只包含质因子2、3、5,所以每一个丑数都是由在它之前的丑数序列中的某一个数乘 2、乘 3 或乘 5 得到的,于是我们可以想到动态规划的解法。
**(确定递推公式)从前往后看:**已有的丑数序列中的每一个丑数都应该乘以2、乘以3、乘以5,将其值记录到丑数序列中,这样才不会漏掉某一个丑数。但是因为前面的数乘以 5 的值可能会大于后面的数乘以 2 或 3 的值,所以我们不能单纯地按顺序记录丑数序列中每一个数乘2、乘3、乘5的结果。
所以我们可以使用下标的方式:
- 使用下标 a 表示下标 a 之前的每一个丑数乘 2 的结果都已被加入到丑数序列中
- 使用下标 b 表示下标 b 之前的每一个丑数乘 3 的结果都已被加入到丑数序列中
- 使用下标 c 表示下标 c 之前的每一个丑数乘 5 的结果都已被加入到丑数序列中
在构造丑数序列时,选取下标为 a 的丑数乘 2 的结果、下标为 b 的丑数乘 3 的结果、下标为 c 的丑数乘 5 的结果中的最小值,先加入到丑数序列中,然后对应的下标递增。
三 关键点/重点/难点
难点在确定递推公式(上文说得比较清楚了)。
另外一个易错点在,丑数 x 乘 2 的结果可能与丑数 y 乘 3 的结果相等,并且作为最小值被记录到丑数序列中,此时 a 和 b 都应该递增,所以要注意下标递增的写法:if 语句时并列的。
四 代码分析
class Solution {
public:
int nthUglyNumber(int n) {
int dp[n];
//dp[i]: 第i+1个丑数
dp[0] = 1;
int a = 0, b = 0, c = 0;
for(int i = 1; i < n; ++i) {
dp[i] = min(min(dp[a] * 2, dp[b] * 3), dp[c] * 5);
if(dp[i] == dp[a] * 2) ++a;
if(dp[i] == dp[b] * 3) ++b;
if(dp[i] == dp[c] * 5) ++c;
}
return dp[n - 1];
}
};
(五)一题多解
没有
(六) 知识扩展
C 中的变长数组:
在 C99 以后,支持变长数组,也就是说,数组的大小是可以在运行时确定的,它的内存依旧是分配在栈区的。
//可以正常运行
int size;
printf("enter the array size: ");
scanf("%d",&size);
int a[size];
但变长数组的一个明显缺点是,如果 size 很大,则数组的分配可能会失败,并且无法检查分配是否失败,将发生运行时错误(例如,segmentfault)。因此,如果数组大小太大时,需要避免使用变长数组。除此之外,变长数组的作用域仅仅是块范围,如果需要更大的作用域,建议使用动态数组。
留疑:当变长数组为多大时会发生栈溢出呢?