264. Ugly Number II
Problem Description
An ugly number is defined as a positive integer that only has prime factors of 2, 3, and 5. In other words, when you break down an ugly number into its prime factors, you'll only find combinations of these three primes.
For example:
- 1is considered an ugly number (by convention, as it has no prime factors)
- 2,- 3,- 4,- 5,- 6,- 8,- 9,- 10,- 12are all ugly numbers
- 2 = 2
- 6 = 2 × 3
- 12 = 2² × 3
- 7,- 11,- 14are NOT ugly numbers because:- 7has prime factor- 7
- 11has prime factor- 11
- 14 = 2 × 7(contains prime factor- 7)
 
The problem asks you to find the nth ugly number in the sequence when ugly numbers are arranged in ascending order.
The sequence of ugly numbers starts as: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...
Given an integer n, you need to return the nth number in this sequence. For instance:
- If n = 1, return1
- If n = 10, return12
- If n = 11, return15
Intuition
The key observation is that every ugly number (except 1) is formed by multiplying a smaller ugly number by 2, 3, or 5. This means if we have an ugly number x, then 2x, 3x, and 5x are also ugly numbers.
Starting with the smallest ugly number 1, we can generate new ugly numbers by multiplying it by 2, 3, and 5, giving us 2, 3, and 5. Then from 2, we can generate 4, 6, and 10. From 3, we get 6, 9, and 15, and so on.
The challenge is maintaining the order - we need to generate ugly numbers in ascending order to find the nth one. If we simply generate all possible ugly numbers recursively, we'd create duplicates (like 6 from both 2×3 and 3×2) and lose track of the ordering.
The solution uses a min-heap (priority queue) to always extract the smallest unprocessed ugly number. Here's the thought process:
- Start with 1in the heap
- Extract the smallest number from the heap - this is our next ugly number in sequence
- Generate three new candidates by multiplying this number by 2,3, and5
- Add these new candidates to the heap (if not already seen)
- Repeat until we've found nugly numbers
The heap automatically maintains the sorted order for us - we always get the next smallest ugly number when we pop from it. The visited set prevents us from adding duplicates (like when both 2×3 and 3×2 produce 6).
This approach elegantly handles the generation and ordering problem simultaneously - we generate ugly numbers on-the-fly in sorted order, rather than generating many numbers and then sorting them.
Solution Approach
The implementation uses a min-heap combined with a hash set to efficiently generate ugly numbers in ascending order.
Data Structures Used:
- h: A min-heap (priority queue) that stores candidate ugly numbers, always keeping the smallest at the top
- vis: A hash set that tracks which ugly numbers we've already seen to avoid duplicates
- ans: Variable to store the current ugly number being processed
Algorithm Steps:
- 
Initialize the heap with [1]and the visited set with{1}. Setans = 1as our starting point.
- 
Main Loop - Repeat ntimes:- Extract minimum: Pop the smallest number from the heap using heappop(h)and store it inans
- Generate new candidates: For each prime factor in [2, 3, 5]:- Calculate nxt = ans * vwherevis the current prime factor
- If nxthasn't been seen before (not invis):- Add nxtto the visited set
- Push nxtonto the heap
 
- Add 
 
- Calculate 
 
- Extract minimum: Pop the smallest number from the heap using 
- 
Return ansafterniterations - this will be thenth ugly number
Why This Works:
The algorithm maintains the invariant that the heap always contains unprocessed ugly numbers in sorted order. Each iteration:
- Removes the smallest unprocessed ugly number (guaranteed to be the next in sequence)
- Generates exactly three potential new ugly numbers from it
- The heap automatically maintains sorted order for future iterations
Example Walkthrough for n = 10:
Iteration 1: Pop 1, generate 2, 3, 5 → heap: [2, 3, 5] Iteration 2: Pop 2, generate 4, 6, 10 → heap: [3, 4, 5, 6, 10] Iteration 3: Pop 3, generate 6(skip), 9, 15 → heap: [4, 5, 6, 9, 10, 15] ... Iteration 10: Pop 12 → Return 12
The time complexity is O(n log n) due to heap operations, and space complexity is O(n) for storing the generated ugly numbers in the heap and visited set.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's find the 7th ugly number using the heap-based approach.
Initial Setup:
- Heap: [1]
- Visited: {1}
- Answer: 1
Iteration 1 (n=1):
- Pop 1from heap → ans =1
- Generate: 1×2=2,1×3=3,1×5=5
- Add all to heap (none visited yet)
- Heap: [2, 3, 5], Visited:{1, 2, 3, 5}
Iteration 2 (n=2):
- Pop 2from heap → ans =2
- Generate: 2×2=4,2×3=6,2×5=10
- Add all to heap
- Heap: [3, 4, 5, 6, 10], Visited:{1, 2, 3, 4, 5, 6, 10}
Iteration 3 (n=3):
- Pop 3from heap → ans =3
- Generate: 3×2=6(skip, already visited),3×3=9,3×5=15
- Add 9and15to heap
- Heap: [4, 5, 6, 9, 10, 15], Visited:{1, 2, 3, 4, 5, 6, 9, 10, 15}
Iteration 4 (n=4):
- Pop 4from heap → ans =4
- Generate: 4×2=8,4×3=12,4×5=20
- Add all to heap
- Heap: [5, 6, 8, 9, 10, 12, 15, 20], Visited:{1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 20}
Iteration 5 (n=5):
- Pop 5from heap → ans =5
- Generate: 5×2=10(skip),5×3=15(skip),5×5=25
- Add 25to heap
- Heap: [6, 8, 9, 10, 12, 15, 20, 25]
Iteration 6 (n=6):
- Pop 6from heap → ans =6
- Generate: 6×2=12(skip),6×3=18,6×5=30
- Add 18and30to heap
- Heap: [8, 9, 10, 12, 15, 18, 20, 25, 30]
Iteration 7 (n=7):
- Pop 8from heap → ans =8
- We've completed 7 iterations, so return 8
Result: The 7th ugly number is 8.
The sequence so far: 1, 2, 3, 4, 5, 6, 8, ...
Solution Implementation
1import heapq
2
3class Solution:
4    def nthUglyNumber(self, n: int) -> int:
5        """
6        Find the nth ugly number.
7        An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5.
8      
9        Args:
10            n: The position of the ugly number to find (1-indexed)
11          
12        Returns:
13            The nth ugly number
14        """
15        # Min heap to store ugly numbers in ascending order
16        min_heap = [1]
17      
18        # Set to track visited numbers and avoid duplicates
19        visited = {1}
20      
21        # Variable to store the current ugly number
22        current_ugly = 1
23      
24        # Generate n ugly numbers
25        for _ in range(n):
26            # Extract the smallest ugly number from the heap
27            current_ugly = heapq.heappop(min_heap)
28          
29            # Generate new ugly numbers by multiplying with prime factors 2, 3, 5
30            for prime_factor in [2, 3, 5]:
31                next_ugly = current_ugly * prime_factor
32              
33                # Add to heap only if not already visited
34                if next_ugly not in visited:
35                    visited.add(next_ugly)
36                    heapq.heappush(min_heap, next_ugly)
37      
38        # Return the nth ugly number
39        return current_ugly
401class Solution {
2    public int nthUglyNumber(int n) {
3        // Use a set to track visited ugly numbers to avoid duplicates
4        Set<Long> visitedNumbers = new HashSet<>();
5      
6        // Min heap to get the smallest ugly number each time
7        PriorityQueue<Long> minHeap = new PriorityQueue<>();
8      
9        // Prime factors that define ugly numbers
10        int[] primeFactors = new int[] {2, 3, 5};
11      
12        // Initialize with 1 (the first ugly number)
13        minHeap.offer(1L);
14        visitedNumbers.add(1L);
15      
16        long currentUglyNumber = 0;
17      
18        // Extract the nth smallest ugly number
19        while (n-- > 0) {
20            // Get the smallest ugly number from the heap
21            currentUglyNumber = minHeap.poll();
22          
23            // Generate new ugly numbers by multiplying with each prime factor
24            for (int factor : primeFactors) {
25                long nextUglyNumber = currentUglyNumber * factor;
26              
27                // Only add to heap if this number hasn't been seen before
28                if (visitedNumbers.add(nextUglyNumber)) {
29                    minHeap.offer(nextUglyNumber);
30                }
31            }
32        }
33      
34        // Return the nth ugly number
35        return (int) currentUglyNumber;
36    }
37}
381class Solution {
2public:
3    int nthUglyNumber(int n) {
4        // Min heap to store ugly numbers in ascending order
5        priority_queue<long, vector<long>, greater<long>> minHeap;
6      
7        // Set to track visited numbers and avoid duplicates
8        unordered_set<long> visited{{1L}};
9      
10        // Start with the first ugly number
11        minHeap.push(1L);
12      
13        // Variable to store the current ugly number
14        long currentUglyNumber = 1;
15      
16        // Prime factors that define ugly numbers
17        vector<int> primeFactors = {2, 3, 5};
18      
19        // Process n ugly numbers
20        while (n--) {
21            // Extract the smallest ugly number from the heap
22            currentUglyNumber = minHeap.top();
23            minHeap.pop();
24          
25            // Generate new ugly numbers by multiplying with prime factors
26            for (int& prime : primeFactors) {
27                long nextUglyNumber = currentUglyNumber * prime;
28              
29                // Add to heap only if not already visited
30                if (!visited.count(nextUglyNumber)) {
31                    visited.insert(nextUglyNumber);
32                    minHeap.push(nextUglyNumber);
33                }
34            }
35        }
36      
37        // Return the nth ugly number
38        return static_cast<int>(currentUglyNumber);
39    }
40};
411/**
2 * Finds the nth ugly number.
3 * Ugly numbers are positive integers whose only prime factors are 2, 3, or 5.
4 * @param n - The position of the ugly number to find (1-indexed)
5 * @returns The nth ugly number
6 */
7function nthUglyNumber(n: number): number {
8    // Initialize dynamic programming array with the first ugly number (1)
9    const dp: number[] = [1];
10  
11    // Pointers for multiples of 2, 3, and 5
12    let pointer2: number = 0;
13    let pointer3: number = 0;
14    let pointer5: number = 0;
15  
16    // Generate ugly numbers until we reach the nth one
17    for (let i = 1; i < n; i++) {
18        // Calculate next candidates by multiplying with 2, 3, and 5
19        const nextMultipleOf2: number = dp[pointer2] * 2;
20        const nextMultipleOf3: number = dp[pointer3] * 3;
21        const nextMultipleOf5: number = dp[pointer5] * 5;
22      
23        // Select the minimum value as the next ugly number
24        const nextUglyNumber: number = Math.min(
25            nextMultipleOf2, 
26            Math.min(nextMultipleOf3, nextMultipleOf5)
27        );
28      
29        // Move pointers forward if their value was selected
30        // Multiple pointers can move if there are duplicates
31        if (nextUglyNumber === nextMultipleOf2) {
32            pointer2++;
33        }
34        if (nextUglyNumber === nextMultipleOf3) {
35            pointer3++;
36        }
37        if (nextUglyNumber === nextMultipleOf5) {
38            pointer5++;
39        }
40      
41        // Add the next ugly number to the array
42        dp.push(nextUglyNumber);
43    }
44  
45    // Return the nth ugly number (0-indexed array, so n-1)
46    return dp[n - 1];
47}
48Time and Space Complexity
Time Complexity: O(n log n)
The algorithm uses a min-heap to generate ugly numbers in ascending order. For each of the n iterations:
- We perform one heappop()operation:O(log k)wherekis the current heap size
- We perform up to 3 heappush()operations (for multipliers 2, 3, and 5):3 * O(log k)
- The set membership check nxt not in visisO(1)on average
- The set insertion vis.add(nxt)isO(1)on average
The heap size grows as we generate numbers, but in the worst case, the heap contains at most O(n) elements (since we only pop n elements total and push at most 3n elements). Therefore, each iteration has O(log n) complexity, and with n iterations, the total time complexity is O(n log n).
Space Complexity: O(n)
The space is used by:
- The min-heap h: In the worst case, it can containO(n)ugly numbers waiting to be processed
- The visited set vis: It stores all unique ugly numbers generated, which is bounded byO(n)since we generate at most3nnumbers (3 multipliers for each ofniterations)
Both data structures can grow to O(n) size, so the overall space complexity is O(n).
Common Pitfalls
1. Integer Overflow for Large n Values
When computing the nth ugly number for large values of n (e.g., n > 1000), the ugly numbers can grow exponentially large. Multiplying these large numbers by 2, 3, or 5 might exceed integer limits in some programming languages.
Problem Example:
# For n = 1690, the ugly number is 2^31 = 2,147,483,648 # Further multiplications could cause overflow in languages with fixed integer sizes
Solution:
- In Python, this isn't an issue due to arbitrary precision integers
- In other languages like Java or C++, use long/long long data types
- Add overflow checks if necessary:
MAX_INT = 2**31 - 1 if current_ugly > MAX_INT // 5: # Check before multiplication # Handle potential overflow pass
2. Duplicate Generation Without Proper Tracking
A critical mistake is forgetting to check for duplicates before adding to the heap, leading to redundant processing and incorrect results.
Incorrect Implementation:
# WRONG: Missing duplicate check for prime_factor in [2, 3, 5]: next_ugly = current_ugly * prime_factor heapq.heappush(min_heap, next_ugly) # Duplicates will be added!
Why This Fails:
- Number 6 can be generated as both 2×3 and 3×2
- Without deduplication, 6 appears multiple times in the heap
- This causes the same number to be counted multiple times, returning wrong results
Correct Solution: Always maintain a visited set and check membership before adding:
if next_ugly not in visited: visited.add(next_ugly) heapq.heappush(min_heap, next_ugly)
3. Off-by-One Error in Loop Count
Using range(n-1) or range(n+1) instead of range(n) will return the wrong ugly number.
Common Mistake:
# WRONG: Returns (n-1)th ugly number
for _ in range(n-1):
    current_ugly = heapq.heappop(min_heap)
Correct Approach: The loop should run exactly n times to get the nth number:
for _ in range(n):
    current_ugly = heapq.heappop(min_heap)
4. Memory Consumption for Very Large n
The visited set grows linearly with n and can consume significant memory for large inputs.
Issue:
- For n = 10,000, the visited set might contain 30,000+ numbers
- Each number also grows larger, consuming more memory
Optimization: Consider using the Dynamic Programming approach with three pointers for better space efficiency:
def nthUglyNumber(self, n: int) -> int:
    ugly = [0] * n
    ugly[0] = 1
    i2 = i3 = i5 = 0
  
    for i in range(1, n):
        next_2 = ugly[i2] * 2
        next_3 = ugly[i3] * 3
        next_5 = ugly[i5] * 5
      
        ugly[i] = min(next_2, next_3, next_5)
      
        if ugly[i] == next_2: i2 += 1
        if ugly[i] == next_3: i3 += 1
        if ugly[i] == next_5: i5 += 1
  
    return ugly[n-1]
This approach uses O(n) space instead of potentially O(3n) space.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___ in the given code snippet.
1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
121public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
161function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!