js(70)~[264] 丑数 II === 面试题 17.09. 第 k 个数(18)

133 阅读3分钟

本题力扣传送门

这个题之前写过,先上上一次写的链接 # 面试题 17.09. 第 k 个数(18)

这个题我觉得难点就怎么求的 n个质数的数组,上一次第k个数,听的我一脸蒙蔽,只知道代码比较简单,也不知道,原理是什么,通过现在看题解,知道这种解体思路是三指针动态规划

然而这次我看官方题解js是用最小堆实现的,看的不是太明白,还是怎么实现n个质数的数组,然后这几天一直在写大顶堆小顶堆,就再撸一遍,然后之前看人家实现大小顶堆给堆里面传送一个表达式也明白了,大小顶堆的判断逻辑,不一样的重点就是判断根节点和左右子节点.

方法一 小顶堆

/*
 * @lc app=leetcode.cn id=264 lang=javascript
 *
 * [264] 丑数 II
 */

// @lc code=start
/**
 * @param {number} n
 * @return {number}
 */
const defaultCmp = (x, y) => x < y;
const swap = (arr, i, j) => [arr[i], arr[j]] = [arr[j], arr[i]];
class Heap {
	constructor(cmp = defaultCmp) {
		this.container = [];
		this.cmp = cmp;
	}
	// 从下往上 入堆操作
	insert(data) {
		const { container, cmp } = this;
		container.push(data);
		let index = this.size() - 1;
		while (index) {
			let parent = index - 1 >> 1;
			// 如果当前元素不大于当前元素的根元素 就停止
			if (!cmp(container[index], container[parent])) {
				return;
			}
			// 否则 交换
			swap(container, index, parent);
			// 上移动
			index = parent;
		}


	}

	//  弹出堆顶元素操作
	pop() {
		const { container, cmp } = this;
		if (!this.size()) {
			return null;
		}

		// 先交换首位节点 
		swap(container, 0, this.size() - 1);
		// 交换完以后 弹出最后一个 即是刚刚交换道末尾的 堆顶元素
		const res = container.pop();

		// 开始 从上往下比较重新排序 需要堆顶坐标一个变量index,和另外一个标量用来做对比叫做exchange
		let index = 0;
		let exchange = 2 * index + 1; // 先赋值做节点
		let length = this.size();
		// 只要不越界就开始循环
		while (exchange < length) {
			// 判断有右节点不越界 并且右子节点比左节点大 则继续交换
			let right = 2 * index + 2;
			if (right < length && cmp(container[right], container[exchange])) {
				exchange = right;
			}
			// exhchage 不比 index 大 就终止 break
			if (!cmp(container[exchange], container[index])) {
				break;
			}
			// 交换exchange和index
			swap(container, exchange, index);
			// 重置index和exchange 为下一步做对比
			index = exchange;
			exchange = 2 * index + 1;
		}
		// 返回弹出的最大值 即堆顶元素
		return res;
	}

	// 堆的个数
	size() {
		return this.container.length;
	}
	// 堆顶元素
	peek() {
		if (this.size()) {
			return this.container[0];
		}
		return null;
	}
}


var nthUglyNumber = function (n) {
	const factor = [2, 3, 5];
	// 创建最小堆
	const minHeap = new Heap();
	// 定义一个map 用来储存n个丑数
	const map = new Set();
	// 最小堆和map都增加1
	minHeap.insert(1);
	map.add(1);
	let ugly;
	// 双重循环找到n个丑数
	for (let i = 0; i < n; i++) {
		ugly = minHeap.pop();
		for (const f of factor) {
			// 下一个丑数
			const nextUgly = f * ugly;
			// 判断map中有没有 因为3*5和5*3值一样 加过都不加了
			if (!map.has(nextUgly)) {
				minHeap.insert(nextUgly);
				map.add(nextUgly);
			}
		}
	}

	return ugly;

};
// @lc code=end

方法二 三指针

var nthUglyNumber = function (n) {
	let arr = [1], p2 = 0, p3 = 0, p5 = 0;
	while (n) {
		let min = Math.min(arr[p2] * 2, arr[p3] * 3, arr[p5] * 5);
		arr.push(min);

		if (arr[p2] * 2 === min) {
			p2++;
		}
		if (arr[p3] * 3 === min) {
			p3++;
		}
		if (arr[p5] * 5 === min) {
			p5++;
		}
		n--;
	}
	// arr 多了第一个1 所以所以多减少一个
	return arr[arr.length - 2];

};