[路飞]_丑数II

160 阅读2分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战

leetcode-264 丑数II

题目介绍

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

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

示例1

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

示例2

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

解题思路

思路一:三指针

根据题目来分析,我们假设这些数都依次存放在一个数组里,那么不在这个数组里面的数,就是质因数包含 2, 3, 5 之外的数

而在这个数组里面的数,可以用 2a * 3b * 5c 来表示,a、b、c 必须是在此数组中的数或 0 或 1(a、b、c 不同时为 0,1 不包含任何质因数)

因此使用 2、3、5 分别与数组中的数相乘的结果必定也是质因数只有 2,3,5的数,所以本题的解题思路就是使用 2、3、5 依次与数组中的值相乘,并将结果存放到数组中

解题步骤

1.定义一个数组,数组中存放元素 1
2.定义 p2, p3, p5 分别指向数组的第一个位置
3.分别计算 2 * p2指向的位置的值3 * p3指向的位置的值5 * p5指向的位置的值,然后比较这3个值的大小
4.将最小的值插入到数组中
5.将计算结果与最小值相等的指针移动到当前位置的下一位
6.重复 3-5 的过程,直到获取到第 n 个数

解题代码

var nthUglyNumber = function(n) {
    let p2 = p3 = p5 = 0
    const ans = [1]
    while (ans.length < n) {
        const min = Math.min(2 * ans[p2], 3 * ans[p3], 5 * ans[p5])
        ans.push(min)
        if(min === 2 * ans[p2]) p2++
        if(min === 3 * ans[p3]) p3++
        if(min === 5 * ans[p5]) p5++
    }
    return ans[ans.length - 1]
};

思路二:优先队列

此题也可以利用优先队列来解决,可以利用小顶堆,每次将堆顶的最小值弹出堆,然后将该值分别和 2, 3, 5 相乘,将结果插入到小顶堆中,然后循环直到找到第 n 个数

1643302044(1).png 从图中可以看出,如果将每个数都与 2, 3, 5 相乘,会出现重复的数字,因此,我们将每个值只与大于等于其最大质因数的质因数相乘
(例如,2 的最大质因数是 2,所以 2 分别和 2, 3, 5 相乘;3 的最大质因数是 3,所以 3 分别和 3, 5 相乘;5 的最大质因数是 5,因此 5 只和 5 相乘)

解题步骤

  1. 创建一个小顶堆
  2. 将 1 作为初始值插入到小顶堆中
  3. 弹出堆顶元素:
  • 如果堆顶元素的最大质因数是 5,则将 堆顶元素 * 5 的值插入到小顶堆中
  • 如果堆顶元素的最大质因数是 3,则将 堆顶元素 * 3堆顶元素 * 5 插入到小顶堆中
  • 如果堆顶元素的最大质因数是 2,则将 堆顶元素 * 2堆顶元素 * 3堆顶元素 * 5插入到小顶堆中
  1. 重复步骤 3,直到从堆顶弹出第 n 个值,则为最后的结果

解题代码

var nthUglyNumber = function(n) {
    // 创建一个小顶堆
    const minHeap = new Heap()
    // 插入初始值 1
    minHeap.push(1)
    let ans
    while(n--) {
        // 弹出堆顶元素
        ans = minHeap.pop()
        // 判断堆顶元素的最大质因数
        if (ans % 5 === 0) {
            minHeap.push(ans * 5)
        } else if (ans % 3 === 0) {
            minHeap.push(ans * 3)
            minHeap.push(ans * 5)
        } else {
            minHeap.push(ans * 2)
            minHeap.push(ans * 3)
            minHeap.push(ans * 5)
        }
    }
    return ans
};

// 小顶堆
class Heap {
    constructor() {
        this.heap = []
    }

    // 插入元素
    push(val) {
        this.heap.push(val)
        this._sortBack()
    }

    // 弹出堆顶元素
    pop() {
        const val = this.heap[0]
        const back = this.heap.pop()
        if(this.heap.length) {
            this.heap[0] = back
            this._sortFront()
        }
        return val
    }

    // 向上调整
    _sortBack() {
        let i = this.heap.length - 1
        while(i > 0 && this.heap[i] < this.heap[Math.floor((i - 1) / 2)]) {
            [this.heap[i], this.heap[Math.floor((i - 1) / 2)]] = [this.heap[Math.floor((i - 1) / 2)], this.heap[i]]
            i = Math.floor((i - 1) / 2)
        }
    }

    // 向下调整
    _sortFront() {
        let i = 0
        while (i * 2 + 1 < this.heap.length) {
            let temp = i
            if (this.heap[temp] > this.heap[i * 2 + 1]) temp = i * 2 + 1
            if (i * 2 + 2 < this.heap.length && this.heap[temp] > this.heap[i * 2 + 2]) temp = i * 2 + 2
            if (temp === i) break
            [this.heap[temp], this.heap[i]] = [this.heap[i], this.heap[temp]]
            i = temp
        }
    }
}