An ugly number is a positive integer whose prime factors are limited to 2
, 3
, and 5
.
Given an integer n
, return the nth
ugly number.
Example 1:
Input: n = 10 Output: 12 Explanation: [1, 2, 3, 4, 5, 6, 8, 9, 10, 12] is the sequence of the first 10 ugly numbers.
Example 2:
Input: n = 1 Output: 1 Explanation: 1 has no prime factors, therefore all of its prime factors are limited to 2, 3, and 5.
Constraints:
1 <= n <= 1690
The problem asks to find the nth ugly number. An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5.
There are two main approaches to solve this problem:
Approach 1: Using a Heap (Priority Queue)
This approach leverages a min-heap to efficiently manage and retrieve the smallest ugly number at each step.
Initialization: Start with a min-heap containing the first ugly number, 1. A set vis
is used to track already encountered ugly numbers, preventing duplicates.
Iteration: Iterate n
times. In each iteration:
ans
) from the heap. This is the next ugly number.ans
with 2, 3, and 5.vis
set (meaning it's a new ugly number), add it to the heap and the vis
set.Result: After n
iterations, the last extracted number (ans
) is the nth ugly number.
Approach 2: Dynamic Programming
This approach uses dynamic programming to build up a list of ugly numbers.
Initialization: Create a DP array dp
of size n
to store the ugly numbers. Initialize dp[0]
to 1. Initialize pointers p2
, p3
, and p5
to 0, which will track the indices of the next multiples of 2, 3, and 5 respectively within the dp
array.
Iteration: Iterate from 1 to n-1
. For each index i
:
next2 = dp[p2] * 2
, next3 = dp[p3] * 3
, next5 = dp[p5] * 5
.dp[i]
.p2
, p3
, and p5
depending on which multiple was selected.Result: dp[n-1]
contains the nth ugly number.
Approach 1 (Heap):
vis
set. Therefore, the amortized time complexity is closer to O(n).Approach 2 (DP):
Approach 1 (Heap):
vis
set store at most O(n) elements in the worst case.Approach 2 (DP):
dp
uses O(n) space.Approach 1 (Heap):
import heapq
class Solution:
def nthUglyNumber(self, n: int) -> int:
h = [1]
vis = {1}
ans = 1
for _ in range(n):
ans = heapq.heappop(h)
for v in [2, 3, 5]:
nxt = ans * v
if nxt not in vis:
vis.add(nxt)
heapq.heappush(h, nxt)
return ans
Approach 2 (DP):
class Solution:
def nthUglyNumber(self, n: int) -> int:
dp = [1] * n
p2 = p3 = p5 = 0
for i in range(1, n):
next2, next3, next5 = dp[p2] * 2, dp[p3] * 3, dp[p5] * 5
dp[i] = min(next2, next3, next5)
if dp[i] == next2:
p2 += 1
if dp[i] == next3:
p3 += 1
if dp[i] == next5:
p5 += 1
return dp[-1]
The DP approach is generally preferred due to its simpler implementation and slightly better time complexity in practice, although both approaches have linear time complexity in the asymptotic sense. The heap based approach might be preferable if you needed to quickly access other ugly numbers beyond the nth one without recomputing.