263. Ugly Number
Problem Description
An ugly number is defined as a positive integer whose only prime factors are 2, 3, and 5. In other words, when you break down the number into its prime factorization, it should only contain the primes 2, 3, and/or 5 (or no prime factors at all, which would be the number 1).
Given an integer n
, you need to determine whether n
is an ugly number or not. Return true
if it is an ugly number, and false
otherwise.
For example:
1
is an ugly number (it has no prime factors)6
is an ugly number (6 = 2 × 3)8
is an ugly number (8 = 2³)14
is not an ugly number (14 = 2 × 7, and 7 is a prime factor other than 2, 3, or 5)
The solution works by repeatedly dividing n
by 2, 3, and 5 as long as possible. If after removing all factors of 2, 3, and 5, we're left with 1, then the original number was an ugly number. If we're left with any other number, it means there are other prime factors present, so it's not an ugly number.
Note that negative numbers and zero are not considered ugly numbers by definition, since ugly numbers must be positive integers.
Intuition
The key insight is that if a number is ugly, it can be expressed as 2^a × 3^b × 5^c
where a, b, and c are non-negative integers. This means we should be able to completely reduce the number to 1 by repeatedly dividing it by 2, 3, and 5.
Think of it like peeling away layers: if we keep removing all factors of 2, 3, and 5 from the number, and there's nothing left (we get 1), then the number was made up entirely of these prime factors. If there's something else remaining, it means there are other prime factors involved.
For instance, take the number 30:
- 30 ÷ 2 = 15
- 15 ÷ 3 = 5
- 5 ÷ 5 = 1
We successfully reduced it to 1, so 30 is ugly.
Now consider 14:
- 14 ÷ 2 = 7
- 7 is not divisible by 2, 3, or 5
We're stuck with 7, which is a prime number other than 2, 3, or 5. So 14 is not ugly.
The algorithm naturally follows: for each of the allowed prime factors (2, 3, 5), divide the number by that factor as many times as possible. After exhausting all divisions by 2, 3, and 5, check if we're left with 1. If yes, the number is ugly; if not, it contains other prime factors and is not ugly.
Solution Approach
The implementation follows a straightforward iterative approach:
-
Handle Edge Cases: First, we check if
n < 1
. Since ugly numbers must be positive integers, any number less than 1 is immediately not ugly, so we returnFalse
. -
Iterative Division: We iterate through each of the allowed prime factors
[2, 3, 5]
. For each prime factorx
:- We use a while loop to repeatedly divide
n
byx
as long asn
is divisible byx
(i.e.,n % x == 0
) - Each time we find that
n
is divisible byx
, we updaten
by performing integer division:n //= x
- This continues until
n
is no longer divisible byx
, effectively removing all factors ofx
fromn
- We use a while loop to repeatedly divide
-
Final Check: After processing all three prime factors, we check if
n == 1
:- If
n
equals 1, it means we successfully factored out all prime components using only 2, 3, and 5, so the number is ugly - If
n
is not 1, there are other prime factors remaining, so the number is not ugly
- If
The algorithm has a time complexity of O(log n)
because in the worst case, we might need to divide n
by 2 repeatedly until we reach 1 (for example, when n
is a power of 2). The space complexity is O(1)
as we only use a constant amount of extra space.
Example walkthrough with n = 12
:
- Initially
n = 12
- Divide by 2:
12 ÷ 2 = 6
, then6 ÷ 2 = 3
, then 3 is not divisible by 2 - Divide by 3:
3 ÷ 3 = 1
, then 1 is not divisible by 3 - Divide by 5: 1 is not divisible by 5
- Final value:
n = 1
, so returnTrue
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 walk through the solution with n = 30
:
Initial State: n = 30
Step 1: Remove all factors of 2
- Check: Is 30 divisible by 2? Yes (30 % 2 = 0)
- Divide: 30 ÷ 2 = 15
- Check: Is 15 divisible by 2? No (15 % 2 = 1)
- Current value:
n = 15
Step 2: Remove all factors of 3
- Check: Is 15 divisible by 3? Yes (15 % 3 = 0)
- Divide: 15 ÷ 3 = 5
- Check: Is 5 divisible by 3? No (5 % 3 = 2)
- Current value:
n = 5
Step 3: Remove all factors of 5
- Check: Is 5 divisible by 5? Yes (5 % 5 = 0)
- Divide: 5 ÷ 5 = 1
- Check: Is 1 divisible by 5? No (1 % 5 = 1)
- Current value:
n = 1
Final Check: Is n == 1
? Yes, so return True
. The number 30 is ugly.
Let's contrast this with a non-ugly number, n = 14
:
Initial State: n = 14
Step 1: Remove all factors of 2
- Check: Is 14 divisible by 2? Yes (14 % 2 = 0)
- Divide: 14 ÷ 2 = 7
- Check: Is 7 divisible by 2? No (7 % 2 = 1)
- Current value:
n = 7
Step 2: Remove all factors of 3
- Check: Is 7 divisible by 3? No (7 % 3 = 1)
- Current value:
n = 7
Step 3: Remove all factors of 5
- Check: Is 7 divisible by 5? No (7 % 5 = 2)
- Current value:
n = 7
Final Check: Is n == 1
? No, n = 7
. Since 7 is a prime factor other than 2, 3, or 5, return False
. The number 14 is not ugly.
Solution Implementation
1class Solution:
2 def isUgly(self, n: int) -> bool:
3 """
4 Determines if a number is an ugly number.
5 An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5.
6
7 Args:
8 n: The integer to check
9
10 Returns:
11 True if n is an ugly number, False otherwise
12 """
13 # Ugly numbers must be positive
14 if n < 1:
15 return False
16
17 # List of allowed prime factors for ugly numbers
18 prime_factors = [2, 3, 5]
19
20 # Remove all factors of 2, 3, and 5 from n
21 for factor in prime_factors:
22 # Keep dividing n by the current factor while it's divisible
23 while n % factor == 0:
24 n //= factor
25
26 # If n becomes 1, it means all its prime factors were 2, 3, or 5
27 # Therefore, it's an ugly number
28 return n == 1
29
1class Solution {
2 /**
3 * Determines if a number is an ugly number.
4 * An ugly number is a positive integer whose only prime factors are 2, 3, and 5.
5 *
6 * @param n the number to check
7 * @return true if n is an ugly number, false otherwise
8 */
9 public boolean isUgly(int n) {
10 // Ugly numbers must be positive
11 if (n < 1) {
12 return false;
13 }
14
15 // Remove all factors of 2
16 while (n % 2 == 0) {
17 n /= 2;
18 }
19
20 // Remove all factors of 3
21 while (n % 3 == 0) {
22 n /= 3;
23 }
24
25 // Remove all factors of 5
26 while (n % 5 == 0) {
27 n /= 5;
28 }
29
30 // If n becomes 1, it means the original number only had factors of 2, 3, and 5
31 // Otherwise, it has other prime factors and is not ugly
32 return n == 1;
33 }
34}
35
1class Solution {
2public:
3 /**
4 * Determines if a number is an ugly number.
5 * An ugly number is a positive integer whose only prime factors are 2, 3, and 5.
6 *
7 * @param n The number to check
8 * @return true if n is an ugly number, false otherwise
9 */
10 bool isUgly(int n) {
11 // Ugly numbers must be positive
12 if (n < 1) {
13 return false;
14 }
15
16 // Remove all factors of 2
17 while (n % 2 == 0) {
18 n /= 2;
19 }
20
21 // Remove all factors of 3
22 while (n % 3 == 0) {
23 n /= 3;
24 }
25
26 // Remove all factors of 5
27 while (n % 5 == 0) {
28 n /= 5;
29 }
30
31 // If n becomes 1, it means the original number only had prime factors 2, 3, and 5
32 // Otherwise, it contains other prime factors and is not ugly
33 return n == 1;
34 }
35};
36
1/**
2 * Determines if a number is an ugly number.
3 * An ugly number is a positive integer whose only prime factors are 2, 3, and 5.
4 *
5 * @param n - The number to check
6 * @returns true if n is an ugly number, false otherwise
7 */
8const isUgly = function (n: number): boolean {
9 // Ugly numbers must be positive
10 if (n < 1) {
11 return false;
12 }
13
14 // Remove all factors of 2
15 while (n % 2 === 0) {
16 n /= 2;
17 }
18
19 // Remove all factors of 3
20 while (n % 3 === 0) {
21 n /= 3;
22 }
23
24 // Remove all factors of 5
25 while (n % 5 === 0) {
26 n /= 5;
27 }
28
29 // If n is reduced to 1, it only had factors of 2, 3, and 5
30 return n === 1;
31};
32
Time and Space Complexity
Time Complexity: O(log n)
The algorithm repeatedly divides n
by 2, 3, and 5 while these divisions are possible. In the worst case:
- Dividing by 2 removes all powers of 2, which takes at most
log₂(n)
iterations - Dividing by 3 removes all powers of 3, which takes at most
log₃(n)
iterations - Dividing by 5 removes all powers of 5, which takes at most
log₅(n)
iterations
The total number of iterations is bounded by log₂(n) + log₃(n) + log₅(n)
, which is O(log n)
since all logarithms differ only by a constant factor.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. It modifies the input value n
in-place and uses a fixed-size list [2, 3, 5]
for iteration. No additional data structures that grow with input size are used.
Common Pitfalls
1. Not Handling Non-Positive Numbers Correctly
A frequent mistake is forgetting to check if the input is positive, or incorrectly handling the edge case of n = 0
or negative numbers.
Incorrect Implementation:
def isUgly(self, n: int) -> bool:
# Missing check for non-positive numbers
for factor in [2, 3, 5]:
while n % factor == 0: # This will cause ZeroDivisionError when n = 0
n //= factor
return n == 1
Why It's Wrong:
- When
n = 0
, the modulo operation0 % factor
returns 0, causing an infinite loop - Negative numbers would be processed incorrectly since they could become -1 after division
Correct Approach:
def isUgly(self, n: int) -> bool:
if n < 1: # Properly handle non-positive numbers
return False
# ... rest of the code
2. Using Recursion Without Proper Base Cases
Some might attempt a recursive solution but forget proper termination conditions.
Problematic Recursive Implementation:
def isUgly(self, n: int) -> bool:
if n == 1:
return True
# Missing check for n < 1
for factor in [2, 3, 5]:
if n % factor == 0:
return self.isUgly(n // factor)
return False
Why It's Wrong:
- Doesn't handle negative numbers or zero, potentially causing stack overflow
- For
n = 0
, it will infinitely recurse since0 // factor = 0
Better Recursive Approach:
def isUgly(self, n: int) -> bool:
if n < 1:
return False
if n == 1:
return True
for factor in [2, 3, 5]:
if n % factor == 0:
return self.isUgly(n // factor)
return False
3. Inefficient Order of Prime Factors
While not incorrect, checking factors in a suboptimal order can lead to more iterations.
Less Efficient:
prime_factors = [5, 3, 2] # Checking larger factors first
More Efficient:
prime_factors = [2, 3, 5] # Checking smaller factors first
Why It Matters: Numbers are more likely to have smaller prime factors. For example, checking and removing all factors of 2 first (which appears most frequently) reduces the number more quickly, making subsequent divisions faster.
4. Integer Overflow Concerns in Other Languages
While Python handles arbitrary precision integers automatically, in languages like Java or C++, you might encounter overflow issues.
Potential Issue in Other Languages:
- Very large numbers might cause overflow during intermediate calculations
- Need to be careful with the data type used (int vs long in Java)
Python Advantage: Python's automatic handling of big integers means this isn't a concern, but it's worth noting when translating the solution to other languages.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended 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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!