「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」
题目介绍
给你一个整数 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 通常被视为丑数。
解题思路
思路一:三指针
根据题目来分析,我们假设这些数都依次存放在一个数组里,那么不在这个数组里面的数,就是质因数包含 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 个数
从图中可以看出,如果将每个数都与 2, 3, 5 相乘,会出现重复的数字,因此,我们将每个值只与大于等于其最大质因数的质因数相乘
(例如,2 的最大质因数是 2,所以 2 分别和 2, 3, 5 相乘;3 的最大质因数是 3,所以 3 分别和 3, 5 相乘;5 的最大质因数是 5,因此 5 只和 5 相乘)
解题步骤
- 创建一个小顶堆
- 将 1 作为初始值插入到小顶堆中
- 弹出堆顶元素:
- 如果堆顶元素的最大质因数是 5,则将
堆顶元素 * 5的值插入到小顶堆中 - 如果堆顶元素的最大质因数是 3,则将
堆顶元素 * 3、堆顶元素 * 5插入到小顶堆中 - 如果堆顶元素的最大质因数是 2,则将
堆顶元素 * 2、堆顶元素 * 3、堆顶元素 * 5插入到小顶堆中
- 重复步骤 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
}
}
}