一、题目简介
LeetCode 263(实际上在力扣国际站为 263)《丑数》(Ugly Number)是一道经典的数论题,考查对因子分解和递归或迭代判断的方法。丑数定义为只包含质因子 2、3、5 的正整数。给定一个整数 n,判断它是否为丑数。
二、题目描述
实现一个函数 isUgly(n)
,如果 n 是丑数就返回 true,否则返回 false。
1 也被视为丑数。
丑数的定义:它的所有质因子只包含 2、3、5。注意 n 可能为负数或者 0。
函数签名:
def isUgly(n: int) -> bool
三、示例分析
示例 1:
输入: n = 6
输出: true
解释:6 = 2 × 3
示例 2:
输入: n = 8
输出: true
解释:8 = 2 × 2 × 2
示例 3:
输入: n = 14
输出: false
解释:14 = 2 × 7,其中 7 不是允许质因子
示例 4:
输入: n = 1
输出: true
解释:1 被视为丑数
示例 5:
输入: n = 0
输出: false
解释:0 不是正整数,故不是丑数
四、解题思路与详细步骤
方案一:迭代除尽法(贪心消除质因子)
步骤详解
- 如果 n ≤ 0,直接返回 False(非正整数)。
- 对 n 不断除以 2、3、5:
- 当 n % 2 == 0,n //= 2;
- 当 n % 3 == 0,n //= 3;
- 当 n % 5 == 0,n //= 5;
重复这三步直到 n 无法被 2、3、5 整除为止。
- 最后判断如果 n == 1,则说明原数仅包含 2、3、5 质因子,是丑数;否则不是。
优缺点
- 时间复杂度 O(log n),每次消除至少会将 n 除以 2、3 或 5,快速缩小 n。
- 空间 O(1),不需要额外存储。
- 思路直观,面试常用。
方案二:递归除尽法
步骤详解
- 递归函数
check(n)
:- 如果 n == 1,返回 True;
- 如果 n ≤ 0 或 n 不能被 2、3、5 整除,则返回 False;
- 如果 n % 2 == 0,返回
check(n // 2)
; - 否则如果 n % 3 == 0,返回
check(n // 3)
; - 否则返回
check(n // 5)
。
- 入口调用
isUgly(n)
时先判断 n ≤ 0 返回 False,否则调用check(n)
。
优缺点
- 与迭代思路等价,但使用递归实现更符合分解思路。
- 递归深度可能较大,但 n 会快速缩小,一般不会超出递归栈深度限制。
- 思路简洁,但需注意递归开销。
五、代码实现(Python/Java/C++)
Python 实现
解法一:迭代除尽
class Solution:
def isUgly(self, n: int) -> bool:
# 丑数定义只包含质因子 2, 3, 5,n 必须为正整数
if n <= 0:
return False
# 不断除以 2、3、5
for p in (2, 3, 5):
while n % p == 0:
n //= p
# 最终若为 1,则只包含 2/3/5,否则包含其他因子
return n == 1
解法二:递归除尽
class Solution:
def isUgly(self, n: int) -> bool:
# 非正数不是丑数
if n <= 0:
return False
return self._check(n)
def _check(self, x: int) -> bool:
# 如果缩减到 1,说明消除干净
if x == 1:
return True
# 如果不能被 2/3/5 整除,则包含其他因子
if x % 2 != 0 and x % 3 != 0 and x % 5 != 0:
return False
# 优先除以 2
if x % 2 == 0:
return self._check(x // 2)
# 再除以 3
if x % 3 == 0:
return self._check(x // 3)
# 否则除以 5
return self._check(x // 5)
Java 实现
解法一:迭代除尽
class Solution {
public boolean isUgly(int n) {
// 丑数必须为正
if (n <= 0) return false;
// 不断除以 2, 3, 5
for (int p : new int[]{2, 3, 5}) {
while (n % p == 0) {
n /= p;
}
}
// n 缩减到 1 则为丑数
return n == 1;
}
}
解法二:递归除尽
class Solution {
public boolean isUgly(int n) {
if (n <= 0) return false;
return check(n);
}
private boolean check(int x) {
if (x == 1) return true;
if (x % 2 != 0 && x % 3 != 0 && x % 5 != 0) return false;
if (x % 2 == 0) return check(x / 2);
if (x % 3 == 0) return check(x / 3);
return check(x / 5);
}
}
C++ 实现
解法一:迭代除尽
class Solution {
public:
bool isUgly(int n) {
// 非正数直接返回 false
if (n <= 0) return false;
// 消除所有 2, 3, 5 因子
for (int p : {2, 3, 5}) {
while (n % p == 0) {
n /= p;
}
}
// 最终若为 1,则仅包含 2/3/5
return n == 1;
}
};
解法二:递归除尽
class Solution {
public:
bool isUgly(int n) {
if (n <= 0) return false;
return check(n);
}
private:
bool check(int x) {
if (x == 1) return true;
if (x % 2 != 0 && x % 3 != 0 && x % 5 != 0) return false;
if (x % 2 == 0) return check(x / 2);
if (x % 3 == 0) return check(x / 3);
return check(x / 5);
}
};
六、复杂度分析
-
解法一(迭代):
- 时间复杂度 O(log n),因为 n 每次至少除以 2/3/5,位数快速减少。
- 空间复杂度 O(1),只使用常数级额外空间。
-
解法二(递归):
- 时间复杂度 O(log n),同理。
- 空间复杂度 O(log n),取决于递归深度(递归栈空间)。
七、边界与细节注意
- n ≤ 0 必须返回 false,包括 0 和负数。
- n == 1 视为丑数,直接返回 true。
- 在迭代除尽时注意:
- 每次
while (n % p == 0)
,n /= p
,以消除所有该质因子。 - 若 n 最终等于 1,则说明只包含 2、3、5;否则存在其他质因子。
- 每次
八、模拟面试环节及答案
1. 问:为什么只需要除尽 2、3、5?
答:
丑数定义为只包含质因子 2、3、5。任何其他质因子在除净 2/3/5 后如果 n > 1,则说明有不允许的因子。
2. 问:递归方案会造成栈溢出吗?
答:
n 每次至少除以 2,递归深度约为 log₂(n),对于 n 在32位范围内,深度极小,不会栈溢出。但若 n 极大(比如 10¹⁸),需考虑迭代方案更稳。
3. 问:哈希或集合能做吗?
答:
可以先不断记录除尽后的因子后判断是否剩余 1。但本题直接位运算/取模法更简单高效,不需要额外结构。
4. 问:如何拓展到其他质因子集合?
答:
只需调整除尽质因子集合,例如若质因子为 {2,3,7},则在迭代中循环除以这三种质因子。
5. 问:负丑数的定义是什么?
答:
题目仅针对正整数定义丑数。若允许负数,可在 n < 0
时先取绝对值,然后再判断。但本题要求 n ≤ 0 返回 false。
九、总结升华
丑数问题考查对质因子分解、循环或递归的掌握。掌握这一方法后,还可延伸到“下一个丑数”(LeetCode 264)等动态规划题目,以及其他只含特定质因子的数列判定,是面试和基础刷题库的必修课题。