丑数

Submission

运行时间: 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)

Explain

该题解采用了动态规划的方法来解决丑数问题。首先创建一个长度为 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)  # 测试函数

Explore

丑数是由另一个丑数乘以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)生成的候选丑数,即使它们的值相同,也应当视为它们各自的生成结果已被使用过。这样做不会影响丑数序列的正确性,反而能保证每个生成的丑数都是基于数组中正确的、顺序的前一个丑数,并且避免了重复计算同一个值作为新丑数。