一起刷LeetCode——丑数(小顶堆/动态规划)

86 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。1是丑数,n不超过1600

来源:力扣(LeetCode) 链接:leetcode.cn/problems/ch…

分析

  • 在看过上一篇文章后,看到这个题目,能比较容易的想到小顶堆

最小堆

  • 从小到大第n个,有序,前n个,小顶堆
  • 1是最小的丑数,为了避免重复,使用set,每次拿堆顶的数去和2,3,5相乘,得到新的丑数
  • 麻烦的地方在于要自己实现一个小顶堆
代码
var nthUglyNumber = function(n) {
    const factors = [2, 3, 5];
    const seen = new Set();
    const heap = new MinHeap();
    seen.add(1);
    heap.insert(1);
    let ugly = 0;
    for (let i = 0; i < n; i++) {
        ugly = heap.pop();
        for (const factor of factors) {
            const next = ugly * factor;
            if (!seen.has(next)) {
                seen.add(next);
                heap.insert(next);
            }
        }
        
    }
    return ugly;
};

class MinHeap {
    constructor() {
        this.heap = [];
    }

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

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

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

    shiftUp(index) {
        if(index === 0) { return; }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }  
    }

    swap(i1, i2) {
        const temp = this.heap[i1];
        this.heap[i1]= this.heap[i2];
        this.heap[i2] = temp;
    }

    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }

    pop() {
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
        return this.heap[0];
    }

    shiftDown(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if (this.heap[rightIndex] < this.heap[index]){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }

    peek() {
        return this.heap[0];
    }

    size() {
        return this.heap.length;
    }
}

动态规划

  • 在使用小顶堆的时候,会算额外的丑数,而且实现一个小顶堆也很麻烦,使用动态规划优化
  • 丑数数组第一个是1,当前因子2、3、5都指向第一个丑数,然后算下一个丑数的时候,这三个因子与所指的数字相乘,最小的那个就是下一个丑数,哪个因子乘出了最小值,这个因子对应的指针向后移动一位
代码
var nthUglyNumber = function(n){
    const dp = [0]
    dp[1] = 1
    let p2 = 1, p3 = 1, p5 = 1
    for(let i=2;i<=n;i++){
        let num2 = dp[p2]*2
        let num3 = dp[p3]*3
        let 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]
}

总结

  • 本题难度等级为中等,可以比较容易相处小顶堆和动态规划的方法
  • 比起小顶堆,还是更推荐动态规划,个人还是喜欢需要哪个数算到哪个数
  • 今天也是有收获的一天