返回

剑指Offer 35:丑数

前端

一、算法概述

在计算机科学中,丑数是指仅由质数 2、3 和 5 这三个质因子组成的正整数。 换句话说,一个数如果能够被 2、3 或 5 整除,则为丑数,否则不为丑数。 例如,6、8 和 15 都是丑数,而 7、11 和 21 不是丑数。

丑数的寻找是一个经典的动态规划问题,其基本思路是利用一个数组 M 记录丑数序列,并从 M 中依次取出丑数作为下一个丑数的候选数。

  1. 初始化数组 M

首先,我们将数组 M 的前三个元素初始化为丑数 1、2 和 3,以满足丑数序列的定义。

  1. 更新数组 M

接下来,我们需要不断更新数组 M,以保持其包含所有丑数。具体来说,我们从 M 中依次取出丑数作为候选数,并将其分别乘以 2、3 和 5 得到三个新的候选数。这三个候选数中,一定会有一个是最小的丑数,将其加入数组 M 即可。

  1. 查找下一个丑数

当数组 M 中的丑数数量达到 n 时,我们就可以从中找到第 n 个丑数。

二、JavaScript 实现

/**
 * 返回第 n 个丑数。
 *
 * @param {number} n
 * @returns {number}
 */
function nthUglyNumber(n) {
  // 初始化丑数数组 M
  const M = [1, 2, 3];

  // 丑数的指数数组,分别对应 2、3 和 5
  const indexes = [0, 0, 0];

  // 丑数的乘积数组,分别对应 2、3 和 5
  const factors = [2, 3, 5];

  // 当丑数数组 M 中的丑数数量达到 n 时,停止循环
  while (M.length < n) {
    // 计算当前最小的丑数
    const minUglyNumber = Math.min(...factors.map((factor, index) => factor * M[indexes[index]]));

    // 将当前最小的丑数添加到数组 M 中
    M.push(minUglyNumber);

    // 更新丑数的指数数组
    for (let i = 0; i < factors.length; i++) {
      if (factors[i] * M[indexes[i]] === minUglyNumber) {
        indexes[i]++;
      }
    }
  }

  // 返回第 n 个丑数
  return M[n - 1];
}

console.log(nthUglyNumber(10)); // 12
console.log(nthUglyNumber(20)); // 20

三、时间复杂度分析

在最坏情况下,算法需要迭代 n 次才能找到第 n 个丑数。在每次迭代中,我们需要比较三个候选数并选择最小的丑数,这需要花费 O(1) 的时间。因此,算法的总时间复杂度为 O(n)。

四、结语

丑数算法是一个经典的动态规划问题,在实际应用中有着广泛的用途。通过利用数组 M 记录丑数序列,我们可以有效地避免重复计算,并在时间复杂度为 O(n) 的情况下求得第 n 个丑数。

我希望这篇文章能够帮助您理解 JavaScript 版的丑数算法,并为您的编程实践提供一些参考。如果您有任何问题或建议,请随时留言评论。