264. 丑数 II (ugly number ii)

3,534 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情

264. 丑数 II 题目描述:给你一个整数 n ,请你找出并返回第 n 个 丑数 。丑数 就是只包含质因数 23 和/或 5 的正整数。n[1,1690] n \in [1, 1690]

示例1示例2
输入n=10n = 10
输出1212
解释[1,2,3,4,5,6,8,9,10,12][1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 1010 个丑数组成的序列。
输入n=1n = 1
输出11
解释11 通常被视为丑数。

中规中矩的动态规划

正向思维方法:判断每一个数是否是丑数,如果是,记录当前数字,并将计数器 +1+1。如果计算器此时等于 nn,返回当前数字即可。如何判断一个数是否是丑数,可参考 # 263. 丑数。本文将采用动态规划的思想来求解第 nn 个丑数。

1、确定 dp 状态数组

定义 dp[i]dp[i] 是第 i 个丑数,其中 i[1,n]i \in [1, n]

NOTE: 为方便计算,dpdp00 位的元素当作哨兵节点,不介入整个状态的转移过程。

2、确定 dp 状态方程

试想丑数是如何计算出来:假设有一个指针 pp,初始状态 p=1p=1 (也是第一个丑数);既然丑数只能有223355 三个因子,那就从初始状态 p=1p=1 不断去 ×2×3×5\times 2、\times 3、\times 5(即,p×2p \times 2p×3p \times 3p×5p \times 5),或者是 ×\times 2、3、5之间的组合(即,p×2×3p \times 2 \times 3p×3×5p \times 3 \times 5p×2×3×5p \times 2 \times 3 \times 5)。为保证计算出来的丑数保持相对大小顺序,运算时应取计算结果的最小值。

定义三个指针 p2p_2p3p_3p5p_5,起始状态为 p2=p3=p5=1p_2=p_3=p_5=1。对于 dp[i]dp[i] 存在,

dp[i]=min(dp[p2]×2,dp[p3]×3,dp[p5]×5)dp[i]= min(dp[p_2] \times 2,dp[p_3] \times 3,dp[p_5] \times 5)

如果此时,

  • dp[i]==dp[p2]×2dp[i] == dp[p_2] \times 2,说明第 i 个丑数归位,此时 p2p_2 指针向前移动一位,即 p2++p_2++
  • dp[i]==dp[p3]×3dp[i] == dp[p_3] \times 3,说明第 i 个丑数归位,此时 p3p_3 指针向前移动一位,即 p3++p_3++
  • dp[i]==dp[p5]×5dp[i] == dp[p_5] \times 5,说明第 i 个丑数归位,此时 p5p_5 指针向前移动一位,即 p5++p_5++

3、确定 dp 初始状态

dp[1]dp[1] 代表第 11 个丑数,即 dp[1]=1dp[1] = 1;

4、确定遍历顺序

i=2i = 2 遍历到 i=ni = n;

5、确定最终返回值

dp[n]dp[n] 是第 nn 个丑数,即为最终返回值。

6、代码示例

/**
 * 空间复杂度 O(n)
 * 时间复杂度 O(n)
 */
function nthUglyNumber(n: number): number {
    const dp = new Array(n + 1).fill(0);
    dp[1] = 1;
    let p2 = 1, p3 = 1, p5 = 1;
    
    for (let i = 2; i <= n; i++) {
        const num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
        dp[i] = Math.min(num2, num3, num5);
        if (dp[i] === num2) {
            p2++;
        } else if (dp[i] === num3){
            p3++;
        } else if (dp[i] === num5) {
            p5++;
        }
    }
    return dp[n];
};

参考