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 <= 1690The goal of #264 Ugly Number II is to find the nth positive number whose prime factors are limited to 2, 3, and 5. A brute force approach that checks every number and factors it would be inefficient for large n. Instead, efficient generation techniques are used.
A common strategy uses dynamic programming with three pointers. The idea is to build the sequence of ugly numbers incrementally. Maintain indices representing the next multiple of 2, 3, and 5. At each step, choose the smallest candidate and advance the corresponding pointer(s). This ensures numbers remain sorted and avoids duplicates.
Another approach uses a min-heap (priority queue) combined with a hash set to repeatedly extract the smallest ugly number and generate its multiples. While simpler conceptually, it introduces additional overhead due to heap operations.
The dynamic programming method is typically preferred because it generates numbers in order with linear time and minimal extra space.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Dynamic Programming (Three Pointers) | O(n) | O(n) |
| Heap + Hash Set | O(n log n) | O(n) |
NeetCode
Use these hints if you're stuck. Try solving on your own first.
The naive approach is to call <code>isUgly</code> for every number until you reach the n<sup>th</sup> one. Most numbers are <i>not</i> ugly. Try to focus your effort on generating only the ugly ones.
An ugly number must be multiplied by either 2, 3, or 5 from a smaller ugly number.
The key is how to maintain the order of the ugly numbers. Try a similar approach of merging from three sorted lists: L<sub>1</sub>, L<sub>2</sub>, and L<sub>3</sub>.
Assume you have U<sub>k</sub>, the k<sup>th</sup> ugly number. Then U<sub>k+1</sub> must be Min(L<sub>1</sub> * 2, L<sub>2</sub> * 3, L<sub>3</sub> * 5).
The idea is to use an array to store the ugly numbers and use three pointers for 2, 3, and 5 to calculate the next potential ugly numbers. We then choose the minimum of these numbers to be the next ugly number and appropriately move the pointers.
Time Complexity: O(n), where n is the number of ugly numbers to generate.
Space Complexity: O(n), for storing the ugly numbers array.
1#include <iostream>
2#include <vector>
3using namespace std;
4
5int nthUglyNumber(int n) {
6 vector<int> ugly(n);
7 ugly[0] = 1;
8 int i2 = 0, i3 = 0, i5 = 0;
9 int next_2 = 2, next_3 = 3, next_5 = 5;
10
11 for (int i = 1; i < n; ++i) {
12 int next_ugly = min(next_2, min(next_3, next_5));
13 ugly[i] = next_ugly;
14
if (next_ugly == next_2) next_2 = ugly[++i2] * 2;
if (next_ugly == next_3) next_3 = ugly[++i3] * 3;
if (next_ugly == next_5) next_5 = ugly[++i5] * 5;
}
return ugly[n - 1];
}
int main() {
int n = 10;
cout << nthUglyNumber(n) << endl; // Output: 12
return 0;
}This C++ solution follows the dynamic programming approach with three pointers. Here, a vector is used to store the sequence of ugly numbers, and pointers i2, i3, and i5 are used in the same way as described for the C solution, determining the smallest next number and updating corresponding pointers.
This method involves using a min-heap to manage the sequence of potential ugly numbers. We start with 1 in the min-heap and repeatedly extract the smallest element, multiplying it by 2, 3, and 5 to generate new candidates, which are then inserted back into the heap. Duplicate entries are avoided by using a set for tracking which numbers have been added to the heap.
Time Complexity: O(n log n), primarily due to heap operations.
Space Complexity: O(n), for data structures that might store up to n numbers.
1#include <iostream>
2#include <queue>
3#include <unordered_set>
4using namespace std;
5
int nthUglyNumber(int n) {
priority_queue<long, vector<long>, greater<long>> minHeap;
unordered_set<long> seen;
minHeap.push(1);
seen.insert(1);
long currUgly = 1;
for (int i = 0; i < n; ++i) {
currUgly = minHeap.top();
minHeap.pop();
if (!seen.count(currUgly * 2)) {
minHeap.push(currUgly * 2);
seen.insert(currUgly * 2);
}
if (!seen.count(currUgly * 3)) {
minHeap.push(currUgly * 3);
seen.insert(currUgly * 3);
}
if (!seen.count(currUgly * 5)) {
minHeap.push(currUgly * 5);
seen.insert(currUgly * 5);
}
}
return (int)currUgly;
}
int main() {
int n = 10;
cout << nthUglyNumber(n) << endl; // Output: 12
return 0;
}Watch expert explanations and walkthroughs
Practice problems asked by these companies to ace your technical interviews.
Explore More ProblemsJot down your thoughts, approach, and key learnings
Yes, variations of the Ugly Number problem are common in technical interviews at companies like Amazon, Google, and Meta. It tests dynamic programming, sequence generation, and handling duplicates efficiently.
Three pointers track the next positions for multiplying existing ugly numbers by 2, 3, and 5. This ensures that all possible future ugly numbers are generated in order while avoiding repeated values.
The most efficient approach uses dynamic programming with three pointers representing multiples of 2, 3, and 5. By iteratively selecting the smallest next candidate, you can generate ugly numbers in sorted order without duplicates in O(n) time.
A dynamic programming array combined with three index pointers is the most efficient structure for this problem. Alternatively, a min-heap with a hash set can also be used to generate numbers in ascending order, though it is slightly slower.
The solution uses a min-heap to keep track of and retrieve the smallest ugly number, avoiding duplicates by using a set. It generates ugly numbers by multiplying the current smallest number by 2, 3, and 5, inserting valid new numbers back into the heap.