剑指offer-丑数:动态规划+最小堆(JavaScript实现)

Posted 心谭小站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer-丑数:动态规划+最小堆(JavaScript实现)相关的知识,希望对你有一定的参考价值。

题目描述:我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

解法 1: 动态规划

因为丑数只包含质因数 2, 3, 5,所以对于下个丑数来说,一定是前面某个丑数乘 3、乘 4 或者乘 5 所得。

准备三个指针 ptr2、ptr3、ptr5,它们指向的数只能乘 2、3 和 5。在循环过程中,每次选取 2 * res[ptr2]3 * res[ptr3]5 * res[ptr5]这三个数中结果最小的数,并且将对应的指针向前移动。有效循环是 n 次,当循环结束后,res 数组中就按从小到大的顺序保存了丑数。

代码如下:

// ac地址:https://leetcode-cn.com/problems/ugly-number-ii/
// 原文地址:https://xxoo521.com/2020-03-10-ugly-number-ii/
/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function(n) {
    const res = new Array(n);
    res[0] = 1;

    let ptr2 = 0, // 下个数字永远 * 2
        ptr3 = 0, // 下个数字永远 * 3
        ptr5 = 0; // 下个数字永远 * 5

    for (let i = 1; i < n; ++i) {
        res[i] = Math.min(res[ptr2] * 2, res[ptr3] * 3, res[ptr5] * 5);
        // 说明前ptr2个丑数*2也不可能产生比i更大的丑数了
        // 所以移动ptr2
        if (res[i] === res[ptr2] * 2) {
            ++ptr2;
        }
        if (res[i] === res[ptr3] * 3) {
            ++ptr3;
        }
        if (res[i] === res[ptr5] * 5) {
            ++ptr5;
        }
    }

    return res[n - 1];
};

时间复杂度是(O(N)),空间复杂度是(O(N))

解法 2: 最小堆

借助最小堆,可以在 (O(LogN)) 时间复杂度内找到当前最小的元素。整体算法流程是:

  • 准备最小堆 heap。准备 map,用于记录丑数是否出现过。
  • 将 1 放入堆中
  • 从 0 开始,遍历 n 次:
    • 取出堆顶元素,放入数组 res 中
    • 用堆顶元素依此乘以 2、3、5
    • 检查结果是否出现过。若没有出现过,那么放入堆中,更新 map
  • 返回 res 最后一个数字

代码实现如下:

// ac地址:https://leetcode-cn.com/problems/ugly-number-ii/
// 原文地址:https://xxoo521.com/2020-03-10-ugly-number-ii/

const defaultCmp = (x, y) => x > y;
const swap = (arr, i, j) => ([arr[i], arr[j]] = [arr[j], arr[i]]);
class Heap {
    /**
     * 默认是最大堆
     * @param {Function} cmp
     */
    constructor(cmp = defaultCmp) {
        this.container = [];
        this.cmp = cmp;
    }

    insert(data) {
        const { container, cmp } = this;

        container.push(data);
        let index = container.length - 1;
        while (index) {
            let parent = Math.floor((index - 1) / 2);
            if (!cmp(container[index], container[parent])) {
                return;
            }
            swap(container, index, parent);
            index = parent;
        }
    }

    extract() {
        const { container, cmp } = this;
        if (!container.length) {
            return null;
        }

        swap(container, 0, container.length - 1);
        const res = container.pop();
        const length = container.length;
        let index = 0,
            exchange = index * 2 + 1;

        while (exchange < length) {
            // 如果有右节点,并且右节点的值大于左节点的值
            let right = index * 2 + 2;
            if (right < length && cmp(container[right], container[exchange])) {
                exchange = right;
            }
            if (!cmp(container[exchange], container[index])) {
                break;
            }
            swap(container, exchange, index);
            index = exchange;
            exchange = index * 2 + 1;
        }

        return res;
    }

    top() {
        if (this.container.length) return this.container[0];
        return null;
    }
}

/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function(n) {
    const heap = new Heap((x, y) => x < y);
    const res = new Array(n);
    const map = {};
    const primes = [2, 3, 5];

    heap.insert(1);
    map[1] = true;
    for (let i = 0; i < n; ++i) {
        res[i] = heap.extract();

        for (const prime of primes) {
            let tmp = res[i] * prime;
            if (!map[tmp]) {
                heap.insert(tmp);
                map[tmp] = true;
            }
        }
    }
    return res[n - 1];
};

时间复杂度是(O(NlogN)), 空间复杂度是(O(N))

更多资料

整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 ??

以上是关于剑指offer-丑数:动态规划+最小堆(JavaScript实现)的主要内容,如果未能解决你的问题,请参考以下文章

剑指Offer 264. 丑数 II(动态规划,Java)

剑指Offer 264. 丑数 II(动态规划,Java)

剑指offer——第二十九天(动态规划“困难”)

LeetCode 313. 超级丑数(最小堆动态规划)/413. 等差数列划分/167. 两数之和 II - 输入有序数组

乱序版 ● 剑指offer每日算法题打卡题解——动态规划(题号19,49,60)

[M思维] lc264. 丑数 II(多路归并+STL堆)