[路飞]_前端算法第七十一弹-丑数 II

270 阅读2分钟

给你一个整数 n ,请你找出并返回第 n丑数

丑数 就是只包含质因数 23 和/或 5 的正整数

示例 1:

输入:n = 10
输出:12
解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例 2:

输入:n = 1
输出:1
解释:1 通常被视为丑数。

丑数 就是只包含质因数 23 和/或 5 的正整数。这又是一道堆的题,这道题我们可以用小顶堆去实现。

初始时堆为空。首先将最小的丑数 1 加入堆。

每次取出堆顶元素,该元素xx为最小丑数,则2x,3x,5x2x,3x,5x 也是丑数。将其入堆。

再取出堆顶,循环n次,此时堆顶即为第n小的丑数。

小顶堆在之前已有实现

var nthUglyNumber = function (n) {
    const factors = [2, 3, 5];
    const seen = new Set();
    const heap = new Heap("small");
    seen.add(1);
    heap.push(1);
    let ugly = 0;
    for (let i = 1; i < n; i++) {
        ugly = heap.pop();
        for (const factor of factors) {
            const next = ugly * factor;
            if (!seen.has(next)) {
                seen.add(next);
                heap.push(next);
            }
        }
    }
    ugly = heap.pop();
    return ugly;
};

小顶堆

class Heap {
  constructor(cmp = "large") {
    if (cmp == "large") {
      this.cmp = this.large;
    } else if (cmp == "small") {
      this.cmp = this.small
    } else {
      this.cmp = cmp
    }
    this.res = [];
    this.cnt = 0;
  }

  push (val) {
    this.cnt++;
    this.res.push(val)
    this.shiftUp(this.cnt - 1)
  }

  pop () {
    this.cnt--;
    const res = this.res[0]
    const pop = this.res.pop()
    if (this.cnt) {
      this.res[0] = pop
      this.shiftDown(0)
    }
    return res
  }

  shiftUp (i) {
    if (i === 0) return
    const par = this.getParentIndex(i)
    if (this.cmp(this.res[par], this.res[i])) {
      this.swap(par, i)
      this.shiftUp(par)
    }
  }

  shiftDown (i) {
    const l = this.getLeftIndex(i)
    const r = this.getRightIndex(i)
    if (l < this.cnt && this.cmp(this.res[i], this.res[l])) {
      this.swap(i, l)
      this.shiftDown(l)
    }
    if (r < this.cnt && this.cmp(this.res[i], this.res[r])) {
      this.swap(i, r)
      this.shiftDown(r)
    }
  }

  getParentIndex (i) {
    return (i - 1) >> 1
  }

  getLeftIndex (i) {
    return i * 2 + 1
  }

  getRightIndex (i) {
    return i * 2 + 2
  }

  large = (a, b) => a < b

  small = (a, b) => a > b;

  swap = (i, j) => [this.res[i], this.res[j]] = [this.res[j], this.res[i]];

  top = () => this.res[0];

  size = () => this.cnt;

  isEmpty = () => this.cnt === 0

}

动态规划

这道题除了使用堆的方法,也可以使用动态规划,也就是三指针的方法

我们先将三个指针都指向1,p2 = 1, p3 = 1, p5 = 1;

我们创建一个数组dp长度为n+1。

将dp[1]=1

我们循环n次,每一次将三个指针进行对比num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5]取出最小值dp[i] = Math.min(Math.min(num2, num3), num5);

将其加入当前位置,取出最小值的指针移动一位。

var nthUglyNumber = function(n) {
    const dp = new Array(n + 1).fill(0);
    dp[1] = 1;
    let p2 = 1, p3 = 1, p5 = 1;
    for (let i = 2; i <= n; i++) {
        const num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
        dp[i] = Math.min(Math.min(num2, num3), num5);
        if (dp[i] === num2) {
            p2++;
        }
        if (dp[i] === num3) {
            p3++;
        }
        if (dp[i] === num5) {
            p5++;
        }
    }
    return dp[n];
};