[路飞]_leetcode刷题_264. 丑数 II

485 阅读2分钟

题目

264. 丑数 II

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

丑数 就是只包含质因数 2、3 和/或 5 的正整数。

 

示例 1:

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

示例 2:

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

思路1 暴力解

这里暴力解的思路,把丑数数列算出来,再返回第n个。 继续往下思路,如果计算丑数数列arr。

  1. 遍历所有的正整数,判断是否是丑数,是就push进数组arr,不是就继续
  2. 判断arr的length=n,就跳出循环不计算了

返回最后一个元素。

但是这里有一个很大的问题,如果判断一个数是丑数?这需要递归,除以2、3、5一直到除不动了,再判断有没有余数。本来丑数就不是连续的,找第100个丑数,遍历的次数是大于100的,越往后大的越多,再加上这一层递归,那肯定超时了。

所以这个方案舍弃

思路2 动态规划

除了1以外,每一个丑数都是由2、3、5相乘得到的,

仔细分析就是由某个丑数和2相乘,或者某个丑数和3相乘,或者某个丑数和5相乘得到的

假设有三条数列

数列1 (丑数数列每一项和2相乘)

[1*2, 2*2, 3*2, 4*2, 5*2, 6*2, 8*2, 9*2, 10*2.....]

数列2 (丑数数列每一项和3相乘)

[1*3, 2*3, 3*3, 4*3, 5*3, 6*3, 8*3, 9*3, 10*3.....]

数列3(丑数数列每一项和5相乘)

[1*5, 2*5, 3*5, 4*5, 5*5, 6*5, 8*5, 9*5, 10*5.....]

我们丑数数列的数都来源于上述三个数列,假设p2、p3、p5分别为三条数列的当前指针,指针从0开始,数列里每有一项被选入丑数数列,则指针++,如果同时有了两个数相等,如3*5和5*3,那么也不要紧,大家都++一下

假设有丑数数列dp,3条数列中排除已经被选中为丑数的数之后,最小的数分则别为dp[p2]\*2,dp[p3]\*3,dp[p5]\*5,

那么每一个丑数都满足dp[i] = Math.min(dp[p2]\*2,dp[p3]\*3,dp[p5]\*5),这个就是状态转移方程

dp[0] = 1,为边界条件

这样我们就可以开始构建丑数数列了

代码如下:

/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function(n) {
    let p2 = 0,p3 = 0,p5 = 0;
    let dp = [1];
    for(let i=0;i<n;i++){
        let min = Math.min(dp[p2]*2,dp[p3]*3,dp[p5]*5);
        if(dp[p2]*2 == min) p2++;
        if(dp[p3]*3 == min) p3++;
        if(dp[p5]*5 == min) p5++
        dp.push(min);
    }
    return dp[n-1]
};

思路3 小顶堆

结合思路2的分析,每一次丑数都是从这3个数列里取出的,我们现在也不要什么指针了

就来暴力的,将上述三个数列结合到一起,以小顶堆方式存储

每一次取出小顶堆堆顶最小的丑数X,同时往小顶堆里推入3个丑数,分别为2*X,3*X,5*X

开始堆为空,将最小的丑数 1 推入堆。

另外需要用一个哈希表来做去重处理。

这样,第n次从堆中取数,取到就是第n个丑数

var nthUglyNumber = function(n) {
    const baseArr = [2, 3, 5];
    const hashSet = new Set();
    const heap = new MinHeap();
    hashSet.add(1);
    heap.offer(1);
    let ugly = 0;
    for (let i = 0; i < n; i++) {
        ugly = heap.poll();
        for (const base of baseArr) {
            const next = ugly * base;
            if (!hashSet.has(next)) {
                hashSet.add(next);
                heap.offer(next);
            }
        }
        
    }
    return ugly;
};

// 最小堆
class MinHeap {
    constructor() {
        this.heap = [];
    }
    offer(value) {
        this.heap.push(value);
        this.siftUp(this.heap.length - 1);
    }
    poll() {
        this.heap[0] = this.heap.pop();
        this.siftDown(0);
        return this.heap[0];
    }
    siftUp(index) {
        if(index === 0) { return; }
        const parentIndex = 2*index +1;
        if(this.heap[parentIndex] > this.heap[index]){
            [this.heap[parentIndex],this.heap[index]] = [this.heap[index],this.heap[parentIndex]]
            this.siftUp(parentIndex);
        }  
    }
    siftDown(index) {
        const leftIndex = index * 2 + 1;
        const rightIndex = index * 2 + 2;
        if (this.heap[leftIndex] < this.heap[index]) {
            [this.heap[leftIndex],this.heap[index]] = [this.heap[index],this.heap[leftIndex]]
            this.siftDown(leftIndex);
        }
        if (this.heap[rightIndex] < this.heap[index]){
            [this.heap[rightIndex],this.heap[index]] = [this.heap[index],this.heap[rightIndex]]
            this.siftDown(rightIndex);
        }
    }
}