每日LeetCode : 丑数

77 阅读4分钟

题目描述

丑数是指只包含质因数 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;
}

算法流程:

  1. 边界处理
    • 若 n <= 0,直接返回 false(丑数定义为正整数)。
    • 若 n == 1,直接返回 true(1 被视为丑数)。
  2. 递归处理
    • 若 n 能被 2 整除,则递归调用 isUgly(n / 2)
    • 若 n 能被 3 整除,则递归调用 isUgly(n / 3)
    • 若 n 能被 5 整除,则递归调用 isUgly(n / 5)
  3. 终止条件
    • 当 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;
}

算法流程:

  1. 边界处理
    • 若 n <= 0,直接返回 false。
  2. 迭代分解
    • 对每个质因数(2、3、5)进行循环处理,直到 n 不能被该因数整除。
  3. 最终判断
    • 若 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;
}

算法流程:

  1. 边界处理
    • 若 n <= 0,直接返回 false。
  2. 迭代处理
    • 在每次循环中依次尝试除以 2、3、5。
    • 若当前 n 无法被这三个数整除,则返回 false。
  3. 终止条件
    • 当 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;
}

算法流程:

  1. 边界处理
    • 若 n <= 0,直接返回 false。
  2. 通用分解
    • 遍历指定的质因数列表(默认为 [2, 3, 5]),依次将 n 完全除以每个因数。
  3. 最终判断
    • 若 n 最终变为 1,说明所有质因数均在指定集合中;否则,包含其他质因数。

时间复杂度:O(k * log n):其中 k 是质因数列表的长度。

空间复杂度:O(1):仅使用常量级额外空间。


拓展

  1. 丑数序列
    • 序列示例:1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...
    • 生成方法:使用最小堆或动态规划生成第 n 个丑数。
  2. 数学特征
    • 丑数可表示为 2a×3b×5c2^a \times 3^b \times 5^c(a, b, c ≥ 0)。
    • 所有丑数的集合是无限的,但可以通过特定算法高效生成。

总结

  1. 质因数分解本质:丑数的判断本质上是质因数分解的简化版,仅允许特定因数。
  2. 分治策略:将大问题拆解为小问题(递归)或逐步处理(迭代)。
  3. 边界处理
    • 负数和 0 直接排除。
    • 1 的特殊处理是算法设计的关键。
  4. 优化方向
    • 空间优化:递归 → 迭代。
    • 时间优化:提前终止循环。
    • 通用性:参数化质因数集合。