The Ugly Number II problem asks you to find the n
th "ugly number". An ugly number is a positive integer whose prime factors are limited to 2
, 3
, and 5
. In other words, ugly numbers are numbers of the form 2^a * 3^b * 5^c
where a, b, c
are non-negative integers.
1
(by convention), followed by 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...
n
(typically 1 ≤ n ≤ 1690
), and you must return the n
th ugly number in the sequence.2
, 3
, and 5
is allowed to generate new ugly numbers from the previous ones.
At first glance, you might consider checking each integer in order and testing if its only prime factors are 2
, 3
, and 5
. However, this brute-force approach is highly inefficient, especially as n
increases, because most numbers are not ugly numbers.
Instead, we can notice that every ugly number (after 1) is produced by multiplying a previous ugly number by either 2
, 3
, or 5
. This hints at a way to generate the sequence in order, without skipping or duplicating any numbers. The challenge is to do this efficiently and without generating duplicates.
The key insight is to keep track of the next multiple of 2
, 3
, and 5
for the ugly numbers we have so far. By always choosing the smallest next candidate, we can build the sequence in order.
To efficiently generate the sequence of ugly numbers, we use a dynamic programming approach with three pointers. Here’s a step-by-step breakdown:
ugly
of size n
to store the ugly numbers. Set ugly[0] = 1
since 1 is the first ugly number.
i2
, i3
, and i5
. These track which ugly number to multiply by 2
, 3
, and 5
next.
next2 = ugly[i2] * 2
next3 = ugly[i3] * 3
next5 = ugly[i5] * 5
next2
, next3
, and next5
as the next ugly number, and append it to the ugly
array.
next2
was the minimum, increment i2
. If multiple values are equal, increment all relevant pointers to avoid duplicates.
ugly
array contains n
numbers.
This approach guarantees that all ugly numbers are generated in ascending order, with no duplicates, and with optimal efficiency.
Let's walk through finding the first 10 ugly numbers (n = 10
):
The 10th ugly number is 12.
n
.n
steps, and each step involves a constant amount of work.The Ugly Number II problem is a classic example where naive brute-force is too slow, but a clever use of pointers and dynamic programming leads to an efficient solution. By keeping track of which multiples of 2, 3, and 5 to use next, we can generate the sequence of ugly numbers in order, without duplicates, and in linear time. This approach is both elegant and practical, making it a great tool for tackling similar sequence-generation problems.
class Solution:
def nthUglyNumber(self, n: int) -> int:
ugly = [1]
i2 = i3 = i5 = 0
for _ in range(1, n):
next2 = ugly[i2] * 2
next3 = ugly[i3] * 3
next5 = ugly[i5] * 5
next_ugly = min(next2, next3, next5)
ugly.append(next_ugly)
if next_ugly == next2:
i2 += 1
if next_ugly == next3:
i3 += 1
if next_ugly == next5:
i5 += 1
return ugly[-1]
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> ugly(n);
ugly[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
for (int i = 1; i < n; ++i) {
int next2 = ugly[i2] * 2;
int next3 = ugly[i3] * 3;
int next5 = ugly[i5] * 5;
int next_ugly = min(next2, min(next3, next5));
ugly[i] = next_ugly;
if (next_ugly == next2) ++i2;
if (next_ugly == next3) ++i3;
if (next_ugly == next5) ++i5;
}
return ugly[n - 1];
}
};
class Solution {
public int nthUglyNumber(int n) {
int[] ugly = new int[n];
ugly[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
for (int i = 1; i < n; i++) {
int next2 = ugly[i2] * 2;
int next3 = ugly[i3] * 3;
int next5 = ugly[i5] * 5;
int nextUgly = Math.min(next2, Math.min(next3, next5));
ugly[i] = nextUgly;
if (nextUgly == next2) i2++;
if (nextUgly == next3) i3++;
if (nextUgly == next5) i5++;
}
return ugly[n - 1];
}
}
var nthUglyNumber = function(n) {
let ugly = [1];
let i2 = 0, i3 = 0, i5 = 0;
for (let i = 1; i < n; i++) {
let next2 = ugly[i2] * 2;
let next3 = ugly[i3] * 3;
let next5 = ugly[i5] * 5;
let nextUgly = Math.min(next2, next3, next5);
ugly.push(nextUgly);
if (nextUgly === next2) i2++;
if (nextUgly === next3) i3++;
if (nextUgly === next5) i5++;
}
return ugly[n - 1];
};