chencl1986 / Blog

Welcome to lee's blog.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

LeetCode题解:剑指 Offer 49. 丑数,三指针,JavaScript,详细注释

chencl1986 opened this issue · comments

原题链接:剑指 Offer 49. 丑数

解题思路:

  1. 参考了详细通俗的思路分析,多解法中的解法三。
  2. 在使用堆的解法中,每一个丑数都是由之前最小的丑数分别乘以2、3、5计算而来,生成的序列如下:

1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, …

如果将其拆分则是下面这样的:

1, 1×2, 1×3, 2×2, 1×5, 2×3, 2×4, 3×3, 3×4, 3×5...

  1. 按照上面的规律,可以将其按照各个质因数将其拆分成下面三组:
乘 2: 1×2, 2×2, 3×2, 4×2, 5×2, 6×2, 8×2, 9×2,…
乘 3: 1×3, 2×3, 3×3, 4×3, 5×3, 6×3, 8×3, 9×3,…
乘 5: 1×5, 2×5, 3×5, 4×5, 5×5, 6×5, 8×5, 9×5,…

我们可以注意到,被乘数的规律和丑数是一样的。

  1. 我们可以使用三个指针,分别表示上面三组的被乘数在丑数序列中的位置,也就知道了它当前所需要进行的计算。例如当前乘2指针指向的是丑数5,就表示当前乘2指针需要进行5×2运算。
  2. 三个指针分别计算出的结果,取最小值,就是下一个丑数的值。同时这个被取最小值的运算,才是当前真实要实行的运算,因此要将对应的指针向前移动一位。
  3. 这样循环计算可以保证每次计算的结果都是最优的,也就保证了丑数从小到大的排列顺序,这样计算n次就得到了第n个丑数。
/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function (n) {
  let ugly = [1]; // 使用数组按从小到大顺序存储丑数,初始值为1保证了循环能够正常启动
  // 质因数包含2、3、5的丑数在ugly数组中的位置指针
  let index3 = 0;
  let index2 = 0;
  let index5 = 0;

  // 循环生成n个丑数,并存入数组
  for (let i = 1; i < n; i++) {
    // 分别以当前2、3、5质因数对应的丑数为基础,计算下一组丑数
    const newUgly2 = ugly[index2] * 2;
    const newUgly3 = ugly[index3] * 3;
    const newUgly5 = ugly[index5] * 5;
    // 将新的一组丑数取最小值,即为新的一个丑数
    const minUgly = Math.min(newUgly2, newUgly3, newUgly5);
    // 将新丑数存入丑数数组,保证了ugly数组从小到大排列
    ugly.push(minUgly);

    // 只有新一组丑数中,值恰好为最小的指针才需要移动
    if (newUgly2 === minUgly) {
      index2++;
    }
    if (newUgly3 === minUgly) {
      index3++;
    }
    if (newUgly5 === minUgly) {
      index5++;
    }
  }

  // ugly数组的最后一个值,即为第n个丑数
  return ugly[ugly.length - 1];
};