LeetCode题练习与总结:丑数 Ⅱ--264
一、题目描述
给你一个整数 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 通常被视为丑数。
提示:
1 <= n <= 1690
二、解题思路
- 创建一个动态规划数组
dp
,其中dp[i]
表示第i
个丑数。初始化dp[1] = 1
,因为 1 通常被视为丑数。 - 创建三个指针
p2
,p3
,p5
,分别表示下一个丑数可以通过乘以 2、3 或 5 得到。初始时,这三个指针都指向 1。 - 对于每个
i
(从 2 到n
),计算dp[i]
的值,它是min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5)
。 - 更新
p2
、p3
和p5
指针,以指向下一个应该乘以 2、3 或 5 的丑数。 - 返回
dp[n]
作为结果。
三、具体代码
class Solution {public int nthUglyNumber(int n) {int[] dp = new int[n + 1];dp[1] = 1;int p2 = 1, p3 = 1, p5 = 1;for (int i = 2; i <= n; i++) {int num2 = dp[p2] * 2, num3 = dp[p3] * 3, 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];}
}
在这个实现中,我们通过动态规划的方法,逐步构建出丑数序列。每个指针 p2
, p3
, p5
保证不会重复计算同一个丑数,并且能够保证丑数序列的顺序。
四、时间复杂度和空间复杂度
1. 时间复杂度
- 初始化
dp
数组需要 O(n) 的时间,因为需要将dp[1]
设置为 1,其余dp[i]
初始化为 0。 - 遍历
dp
数组,计算每个dp[i]
的值,这个操作需要 O(n) 的时间,因为需要从 2 遍历到 n。 - 在每次遍历中,计算
num2
,num3
,num5
以及Math.min
函数的调用都是常数时间操作,因此对于每次迭代,时间复杂度是 O(1)。 - 总的时间复杂度是 O(n) + O(n) * O(1) = O(n)。
2. 空间复杂度
dp
数组需要 O(n) 的空间,因为需要存储 n 个丑数。- 除了
dp
数组,我们只使用了几个额外的变量(p2
,p3
,p5
,num2
,num3
,num5
),它们都是常数空间。 - 因此,总的空间复杂度是 O(n)。
五、总结知识点
-
类定义:
class Solution
定义了一个名为Solution
的类。 -
方法定义:
public int nthUglyNumber(int n)
定义了一个公共方法nthUglyNumber
,它接受一个整数参数n
并返回一个整数。 -
数组的使用:
int[] dp = new int[n + 1];
创建了一个整数数组dp
,用于动态规划,长度为n + 1
以存储从第 1 个到第 n 个丑数。 -
基本循环结构:
for (int i = 2; i <= n; i++)
使用了一个for
循环来迭代数组dp
的索引,从 2 到 n。 -
条件判断:
if (dp[i] == num2) p2++;
等条件判断用于检查当前的丑数是通过乘以 2、3 或 5 得到的,并相应地更新指针。 -
数学运算:使用乘法
*
和最小值函数Math.min
来计算可能的丑数。 -
指针的概念:
p2
,p3
,p5
作为指针,分别跟踪下一个应该乘以 2、3 或 5 的丑数的位置。 -
数组的索引访问:通过
dp[p2]
,dp[p3]
,dp[p5]
等方式访问数组元素,以计算下一个可能的丑数。 -
变量赋值:
dp[i] = Math.min(Math.min(num2, num3), num5);
等语句展示了如何将计算结果赋值给数组元素。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。