持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想,它通过把原问题分解为简单的子问题来解决复杂问题。
丑数 II
给你一个整数 n
,请你找出并返回第 n
个 丑数 。
丑数 就是只包含质因数 2
、3
和/或 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
}
动态规划
思路:
- 定义数组dp,其中dp[i] 表示第 i 个丑数,第 n 个丑数即为dp[n]。
- 由于最小的丑数是 1,因此dp[1]=1。
- 定义三个指针 p2,p3,p5 ,表示下一个丑数是当前指针指向的丑数乘以对应的质因数。初始时,三个指针的值都是 1。
- 当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 的大小。