编写一个程序,找出第 n
个丑数。
丑数就是只包含质因数 2, 3, 5
的正整数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1
是丑数。n
不超过1690。
解题思路
这个问题是之前问题Leetcode 263:丑数(超详细的解法!!!)的提高。这个问题首先可以想到通过暴力法解决,n
不超过1690
,那么我们可以将前1690
个丑数全部找出来。这就很简单了,我们从1
开始向float('inf')
遍历,知道我们找到1690
个丑数即可。
class Solution:
def nthUglyNumber(self, n: int) -> int:
i, num = 0, 1
uglys = list()
while i < n:
if self.isUgly(num):
uglys.append(num)
i += 1
num += 1
return uglys[n-1]
def isUgly(self, num: int) -> bool:
for p in [2, 3, 5]:
while num and num%p == 0:
num //= p
return num == 1
但是上面这种做法会超时。我们其实有一个优雅的暴力解法,我们可以计算出第1690
个丑数是2123366400
,那么我们很容易的通过数学知识将1690
个丑数都找到(丑数就是只包含质因数 2, 3, 5
的正整数)。
class Solution:
ugly = sorted(2**a * 3**b * 5**c for a in range(32) for b in range(20) for c in range(14))
def nthUglyNumber(self, n: int) -> int:
return self.ugly[n - 1]
上面这种解法非常快,但是太取巧了。
我们希望有一个更好的办法,我们知道[1,2,3,4,5]
是丑数,那么我们通过2,3,5
乘上[1,2,3,4,5]
就可以得到新的丑数,例如
(1) 1×2, 2×2, 3×2, 4×2, 5×2, …
(2) 1×3, 2×3, 3×3, 4×3, 5×3, …
(3) 1×5, 2×5, 3×5, 4×5, 5×5, …
接着在通过2,3,5
乘上新产生的丑数,那么又可以产生新的丑数。接着我们需要做的就是对上面的三行数进行归并排序。首先建立一个最小堆,然后将1
加入堆中,此时堆中的最小元素就是1
,将最小元素弹出。我们对最小元素乘上[2,3,5]
,将得到的值插入堆中,接着继续弹出下一个最小值,知道我们遍历完n
个丑数。
import heapq
class Solution:
def nthUglyNumber(self, n: int) -> int:
q = [1]
for _ in range(1, n):
val = heapq.heappop(q)
while q and q[0] == val:
heapq.heappop(q)
for i in [2, 3, 5]:
heapq.heappush(q, i*val)
return q[0]
上面的这个代码真的非常优雅。当然你也可以手写三路归并
class Solution:
def nthUglyNumber(self, n: int) -> int:
ugly = [1] * n
i2 = i3 = i5 = -1
x = v2 = v3 = v5 = 1
for k in range(n):
x = min(v2, v3, v5)
ugly[k] = x
if x == v2:
i2 += 1
v2 = ugly[i2] * 2
if x == v3:
i3 += 1
v3 = ugly[i3] * 3
if x == v5:
i5 += 1
v5 = ugly[i5] * 5
return x
reference:
https://www.geeksforgeeks.org/ugly-numbers/
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!