动态规划之丑数 II

38 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想,它通过把原问题分解为简单的子问题来解决复杂问题。

丑数 II

给你一个整数 n ,请你找出并返回第 n 个 丑数 。

丑数 就是只包含质因数 23 和/或 5 的正整数。

示例 1:

输入:n = 10

输出:12

解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例 2:

输入:n = 1

输出:1

解释:1 通常被视为丑数。

由题意我们可以得出:对于任意一个丑数 x,其与任意的质因数(2、3、5)相乘,结果(2x、3x、5x)仍为丑数。

小根堆

  • 起始先将最小丑数 1 放入队列
  • 每次从队列取出最小值 x,然后将 x 所对应的丑数 2x、3x 和 5x 进行入队。
  • 对步骤 2 循环多次,第 n 次出队的值即是答案。
fun nthUglyNumber(n: Int): Int {
    val factors = intArrayOf(2, 3, 5)
    val seen: MutableSet<Long> = HashSet()
    val heap = PriorityQueue<Long>()
    seen.add(1L)
    heap.offer(1L)
    var ugly = 0
    for (i in 0 until n) {
        val curr = heap.poll()
        ugly = curr.toInt()
        for (factor in factors) {
            val next = curr * factor
            if (seen.add(next)) {
                heap.offer(next)
            }
        }
    }
    return ugly
}

动态规划

思路:

  1. 定义数组dp,其中dp[i] 表示第 i 个丑数,第 n 个丑数即为dp[n]。
  2. 由于最小的丑数是 1,因此dp[1]=1。
  3. 定义三个指针 p2,p3,p5 ,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。初始时,三个指针的值都是 1。
  4. 当i在[2,n]之间的时候,令dp[i]=min(dp[p2]*2,dp[p3]*3,dp[p5]*5),然后分别比较dp[i]和dp[p2]*2dp[p3]*3,dp[p5]*5是否相等,如果相等则将对应的指针加 1。

代码如下:

fun nthUglyNumber(n: Int): Int {
    val dp = IntArray(n + 1)
    dp[1] = 1
    var p2 = 1
    var p3 = 1
    var p5 = 1
    for (i in 2..n) {
        val num2 = dp[p2] * 2
        val num3 = dp[p3] * 3
        val num5 = dp[p5] * 5
        dp[i] = Math.min(Math.min(num2, num3), num5)
        if (dp[i] == num2) {
            p2++
        }
        if (dp[i] == num3) {
            p3++
        }
        if (dp[i] == num5) {
            p5++
        }
    }
    return dp[n]
}

复杂度分析

  • 时间复杂度:O(n)。需要计算数组 dp 中的 n 个元素。

  • 空间复杂度:O(n)。空间复杂度主要取决于数组 dp 的大小。