劍指offer-醜數:動態規劃+最小堆(JavaScript實現)

題目描述:咱們把只包含因子 二、3 和 5 的數稱做醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。javascript

解法 1: 動態規劃

由於醜數只包含質因數 2, 3, 5,因此對於下個醜數來講,必定是前面某個醜數乘 三、乘 4 或者乘 5 所得。java

準備三個指針 ptr二、ptr三、ptr5,它們指向的數只能乘 二、3 和 5。在循環過程當中,每次選取 2 * res[ptr2]3 * res[ptr3]5 * res[ptr5]這三個數中結果最小的數,而且將對應的指針向前移動。有效循環是 n 次,當循環結束後,res 數組中就按從小到大的順序保存了醜數。git

代碼以下:github

// 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 中
    • 用堆頂元素依此乘以 二、三、5
    • 檢查結果是否出現過。若沒有出現過,那麼放入堆中,更新 map
  • 返回 res 最後一個數字

代碼實現以下:this

// 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)\)spa

更多資料

整理不易,若對您有幫助,請給個「關注+點贊」,您的支持是我更新的動力 👇指針

相關文章
相關標籤/搜索