题目
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。
- 遍历所有的正整数,判断是否是丑数,是就push进数组arr,不是就继续
- 判断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);
}
}
}