题目描述
丑数是指只包含质因数 2、3 和 5 的正整数。给定一个整数 n,判断它是否为丑数。
给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。
//示例 1:
输入: n = 6
输出: true
解释: 6 = 2 × 3
、
//示例 2:
输入: n = 1
输出: true
解释: 1 没有质因数。
//示例 3:
输入: n = 14
输出: false
解释: 14 不是丑数,因为它包含了另外一个质因数 7 。
解法1:递归分解法(直观解法)
解题思路:
通过递归的方式不断将 n 分解为更小的子问题,每次尝试用 2、3、5 中的一个因数去除当前数 n,若能整除则递归处理商的结果,重复这一过程,直到 n 无法再被 2、3、5 整除,此时若 n 的值为 1,则说明其仅由 2、3、5 构成,是丑数;否则,包含其他质因数,不是丑数。
示例代码:
function isUgly(n) {
if (n <= 0) return false;
if (n === 1) return true;
if (n % 2 === 0) return isUgly(n / 2);
if (n % 3 === 0) return isUgly(n / 3);
if (n % 5 === 0) return isUgly(n / 5);
return false;
}
算法流程:
- 边界处理:
- 若 n <= 0,直接返回 false(丑数定义为正整数)。
- 若 n == 1,直接返回 true(1 被视为丑数)。
- 递归处理:
- 若 n 能被 2 整除,则递归调用
isUgly(n / 2)。 - 若 n 能被 3 整除,则递归调用
isUgly(n / 3)。 - 若 n 能被 5 整除,则递归调用
isUgly(n / 5)。
- 若 n 能被 2 整除,则递归调用
- 终止条件:
- 当 n 无法被 2、3、5 整除时,若 n == 1,返回 true;否则返回 false。
时间复杂度:O(log n):每次递归将 n 至少除以 2,因此递归次数与 log n 成正比。
空间复杂度:O(log n): 递归调用栈的深度取决于 n 的大小。
解法2:迭代分解法(空间优化)
解题思路:
通过迭代替代递归,避免递归带来的栈空间开销。依次尝试将 n 完全除以 2、3、5,直到无法继续为止,最后判断剩余值是否为 1。
示例代码:
function isUgly(n) {
if (n <= 0) return false;
const factors = [2, 3, 5];
for (const factor of factors) {
while (n % factor === 0) {
n /= factor;
}
}
return n === 1;
}
算法流程:
- 边界处理:
- 若 n <= 0,直接返回 false。
- 迭代分解:
- 对每个质因数(2、3、5)进行循环处理,直到 n 不能被该因数整除。
- 最终判断:
- 若 n 最终变为 1,说明所有质因数均为 2、3、5;否则,包含其他质因数。
时间复杂度:O(log n):每个质因数的处理次数与 log n 成正比。
空间复杂度:O(1):仅使用常量级额外空间。
解法3:数学推导法(提前终止)
解题思路:
在迭代过程中引入提前终止机制,一旦发现无法被 2、3、5 整除的因数,立即返回 false,减少不必要的计算。
示例代码:
function isUgly(n) {
if (n <= 0) return false;
while (n > 1) {
if (n % 2 === 0) n /= 2;
else if (n % 3 === 0) n /= 3;
else if (n % 5 === 0) n /= 5;
else return false; // 提前终止
}
return true;
}
算法流程:
- 边界处理:
- 若 n <= 0,直接返回 false。
- 迭代处理:
- 在每次循环中依次尝试除以 2、3、5。
- 若当前 n 无法被这三个数整除,则返回 false。
- 终止条件:
- 当 n 降低到 1 时,说明所有质因数均已处理完毕,返回 true。
时间复杂度:最坏 O(log n),平均更优:提前终止可减少不必要的除法操作。
空间复杂度O(1):仅使用常量级额外空间。
解法4:质因数分解(通用解法)
解题思路
将算法抽象为通用形式,允许指定任意质因数集合。通过参数化质因数列表,使函数可灵活应对不同需求(如判断“7-丑数”等)。
示例代码:
function isUgly(n, primes = [2, 3, 5]) {
if (n <= 0) return false;
for (const p of primes) {
while (n % p === 0) {
n /= p;
}
}
return n === 1;
}
算法流程:
- 边界处理:
- 若 n <= 0,直接返回 false。
- 通用分解:
- 遍历指定的质因数列表(默认为 [2, 3, 5]),依次将 n 完全除以每个因数。
- 最终判断:
- 若 n 最终变为 1,说明所有质因数均在指定集合中;否则,包含其他质因数。
时间复杂度:O(k * log n):其中 k 是质因数列表的长度。
空间复杂度:O(1):仅使用常量级额外空间。
拓展
- 丑数序列:
- 序列示例:1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...
- 生成方法:使用最小堆或动态规划生成第 n 个丑数。
- 数学特征:
- 丑数可表示为 (a, b, c ≥ 0)。
- 所有丑数的集合是无限的,但可以通过特定算法高效生成。
总结
- 质因数分解本质:丑数的判断本质上是质因数分解的简化版,仅允许特定因数。
- 分治策略:将大问题拆解为小问题(递归)或逐步处理(迭代)。
- 边界处理:
- 负数和 0 直接排除。
- 1 的特殊处理是算法设计的关键。
- 优化方向:
- 空间优化:递归 → 迭代。
- 时间优化:提前终止循环。
- 通用性:参数化质因数集合。