难度: Medium
给你一个整数 n
,请你找出并返回第 n
个 丑数 。
说明:丑数是只包含质因数 2、3 和/或 5 的正整数;1 是丑数。
示例 1:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12
是前 10 个丑数。
提示:
1 <= n <= 1690
注意:本题与主站 264 题相同:https://leetcode-cn.com/problems/ugly-number-ii/
难度: Medium
给你一个整数 n
,请你找出并返回第 n
个 丑数 。
说明:丑数是只包含质因数 2、3 和/或 5 的正整数;1 是丑数。
示例 1:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12
是前 10 个丑数。
提示:
1 <= n <= 1690
注意:本题与主站 264 题相同:https://leetcode-cn.com/problems/ugly-number-ii/
运行时间: 140 ms
内存: 14.9 MB
class Solution: def nthUglyNumber(self, n: int) -> int: dp = [1] * n a, b, c = 0, 0, 0 for i in range(1, n): n2, n3, n5 = 2*dp[a], 3*dp[b], 5*dp[c] dp[i] = min(n2, n3, n5) if dp[i] == n2: a += 1 if dp[i] == n3: b += 1 if dp[i] == n5: c += 1 return dp[-1] if __name__ == '__main__': s = Solution() s.nthUglyNumber(10)
该题解采用了动态规划的方法来解决丑数问题。首先创建一个长度为 n 的数组 dp,其中 dp[i] 存储第 i+1 个丑数。初始化 dp[0] 为 1,因为 1 是第一个丑数。接着使用三个指针 a、b、c 分别指向数组中某个确定的位置,这些位置对应的元素乘以 2、3、5 后可以生成新的丑数。每次循环计算出三个可能的丑数 n2, n3, n5(分别为 dp[a]*2, dp[b]*3, dp[c]*5),取这三个数中最小的一个作为新的丑数添加到 dp 数组中。然后根据所选的丑数来更新对应的指针(如果新丑数等于 n2,则移动 a 指针;如果等于 n3,则移动 b 指针;如果等于 n5,则移动 c 指针)。这样可以保证数组 dp 按从小到大的顺序存储每一个丑数,直到填满。最后 dp[n-1] 就是第 n 个丑数。
时间复杂度: O(n)
空间复杂度: O(n)
class Solution: def nthUglyNumber(self, n: int) -> int: dp = [1] * n # 初始化dp数组,dp[i]表示第i+1个丑数 a, b, c = 0, 0, 0 # 初始化三个指针,分别对应质因数2,3,5 for i in range(1, n): n2, n3, n5 = 2*dp[a], 3*dp[b], 5*dp[c] # 分别计算三个新的可能的丑数 dp[i] = min(n2, n3, n5) # 选择最小的一个作为新的丑数 if dp[i] == n2: a += 1 # 如果选中的是2*dp[a],则将指针a向前移动 if dp[i] == n3: b += 1 # 如果选中的是3*dp[b],则将指针b向前移动 if dp[i] == n5: c += 1 # 如果选中的是5*dp[c],则将指针c向前移动 return dp[-1] # 返回第n个丑数 if __name__ == '__main__': s = Solution() s.nthUglyNumber(10) # 测试函数
丑数是由另一个丑数乘以2、3或5得到的。在动态生成丑数的过程中,我们保持数组dp中的每个丑数都是从小到大排列的。因此,每次计算新的丑数时,我们需要从dp[a]*2、dp[b]*3、dp[c]*5这三个候选丑数中选择最小的一个,保证添加到数组中的新丑数是当前能够生成的最小的丑数,这样可以确保整个数组的有序性,且不会遗漏任何一个丑数。
在某个特定的循环中,可能存在两个或三个候选丑数的值相同的情况。例如,如果dp[a]*2和dp[b]*3的结果相同,那么选择这个值作为新的丑数后,指针a和b都应该向前移动,因为它们各自的当前值已经被用于生成了新的丑数。这种同时移动多个指针的做法可以确保不会重复计算相同的丑数,且每个丑数只被用一次来生成新的更大的丑数。
dp数组的初始化为dp[0]=1,因为1被定义为第一个丑数。这种初始化方式可以为算法提供一个明确的起点,并且避免了预先假设必须计算哪些丑数,这样算法可以动态地基于已知的丑数生成新的丑数。如果预先计算更多的丑数可能导致不必要的计算和错误,而动态计算可以确保每步计算都是必要的,并且在任何时候都可以停止算法而不会有资源浪费。
当两个或三个候选丑数的值相同时,算法应该将所有具有相同值的指针都向前移动。这是因为这些指针分别代表了不同因子(2、3、5)生成的候选丑数,即使它们的值相同,也应当视为它们各自的生成结果已被使用过。这样做不会影响丑数序列的正确性,反而能保证每个生成的丑数都是基于数组中正确的、顺序的前一个丑数,并且避免了重复计算同一个值作为新丑数。