大根堆-第k个最小的素数分数

116 阅读1分钟

786. 第 K 个最小的素数分数

给你一个按递增顺序排序的数组 arr 和一个整数 k 。数组 arr 由 1 和若干 素数  组成,且其中所有整数互不相同。

对于每对满足 0 <= i < j < arr.length 的 i 和 j ,可以得到分数 arr[i] / arr[j] 。

那么第 k 个最小的分数是多少呢?  以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j] 。

 

示例 1:

输入: arr = [1,2,3,5], k = 3
输出: [2,5]
解释: 已构造好的分数,排序后如下所示: 
1/5, 1/3, 2/5, 1/2, 3/5, 2/3
很明显第三个最小的分数是 2/5

示例 2:

输入: arr = [1,7], k = 1
输出: [1,7]

 

提示:

  • 2 <= arr.length <= 1000
  • 1 <= arr[i] <= 3 * 104
  • arr[0] == 1
  • arr[i] 是一个 素数 ,i > 0
  • arr 中的所有数字 互不相同 ,且按 严格递增 排序
  • 1 <= k <= arr.length * (arr.length - 1) / 2

 

进阶: 你可以设计并实现时间复杂度小于 O(n2) 的算法解决此问题吗?

方法一:大跟堆
/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
var kthSmallestPrimeFraction = function(arr, k) {
	const heap = new Heap((a, b) => a[0] * b[1] > b[0] * a[1]);

	for (let i = 0; i < arr.length - 1; i++) {
		for (let j = i + 1; j < arr.length; j++) {
			if (heap.getSize() < k) {
				heap.push([arr[i], arr[j]]);
			} else {
				const top = heap.getTop();
				if (top && top[0] * arr[j] > arr[i] * top[1]) {
					heap.pop();
					heap.push([arr[i], arr[j]]);
				}
			}
		}
	}
	return heap.getTop();
}

class Heap {
	constructor(compare) {
		this.nodeList = [];
		this.compare = typeof compare === 'function' ? compare : this.defaultCompare;
	}

	defaultCompare(a, b) {
		return a > b;
	}

	getSize() {
		return this.nodeList.length;
	}

	getTop() {
		if (this.nodeList.length === 0) {
			return null;
		}
		return this.nodeList[0];
	}

	push(value) {
		this.nodeList.push(value);
		this.up(this.nodeList.length - 1);
	}

	up(index) {
	    const { compare, parent, nodeList } = this;
        let curIndex = index;
		let parentIndex = parent(curIndex);

		while (curIndex > 0 && compare(nodeList[curIndex], nodeList[parentIndex]))
		{
			const temp = nodeList[curIndex];
			nodeList[curIndex] = nodeList[parentIndex];
			nodeList[parentIndex] = temp;
			curIndex = parentIndex;
			parentIndex = parent(curIndex);
		}
	}

	pop() {
		if (this.nodeList.length === 0) {
			return null;
		}

		const top = this.nodeList[0];
		this.nodeList[0] = this.nodeList[this.nodeList.length - 1];
		this.nodeList.pop();
		this.down(0);
		return top;
	}

	down(index) {
		const { compare, left, right, nodeList } = this;
		let curIndex = index;
		while (left(curIndex) < nodeList.length) {
			let target = left(curIndex);
			if (right(curIndex) < nodeList.length && compare(nodeList[right(curIndex)], nodeList[left(curIndex)])) {
				target = right(curIndex);
			}

			if (compare(nodeList[curIndex], nodeList[target])) {
				break;
			}

			const temp = nodeList[curIndex];
			nodeList[curIndex] = nodeList[target];
			nodeList[target] = temp;
			curIndex = target;
		}
	}

	left(index) {
		return index * 2 + 1;
	}

	right(index) {
		return index * 2 + 2;
	}

	parent(index) {
		return index % 2 === 0 ? index / 2 - 1 : (index - 1) / 2;
	}
}
方法二 多路归并 + 小根堆
var kthSmallestPrimeFraction = function(arr, k) {
	const heap = new Heap((a, b) => a[0] * b[1] < b[0] * a[1]);

	for (let i = 1; i < arr.length; i++) {
		heap.push([arr[0], arr[i], 0, i]);
	}

	for (let i = 0; i < k - 1; i ++) {
		const [, , i, j]= heap.pop();
		if (i + 1 < j) {
			heap.push([arr[i + 1], arr[j], i + 1, j]);
		}

	}
	const [a, b, i, j] =  heap.getTop();
	return [a, b];
}


class Heap {
	constructor(compare) {
		this.nodeList = [];
		this.compare = typeof compare === 'function' ? compare : this.defaultCompare;
	}

	defaultCompare(a, b) {
		return a > b;
	}

	getSize() {
		return this.nodeList.length;
	}

	getTop() {
		if (this.nodeList.length === 0) {
			return null;
		}
		return this.nodeList[0];
	}

	push(value) {
		this.nodeList.push(value);
		this.up(this.nodeList.length - 1);
	}

	up(index) {
	    const { compare, parent, nodeList } = this;
        let curIndex = index;
		let parentIndex = parent(curIndex);

		while (curIndex > 0 && compare(nodeList[curIndex], nodeList[parentIndex]))
		{
			const temp = nodeList[curIndex];
			nodeList[curIndex] = nodeList[parentIndex];
			nodeList[parentIndex] = temp;
			curIndex = parentIndex;
			parentIndex = parent(curIndex);
		}
	}

	pop() {
		if (this.nodeList.length === 0) {
			return null;
		}

		const top = this.nodeList[0];
		this.nodeList[0] = this.nodeList[this.nodeList.length - 1];
		this.nodeList.pop();
		this.down(0);
		return top;
	}

	down(index) {
		const { compare, left, right, nodeList } = this;
		let curIndex = index;
		while (left(curIndex) < nodeList.length) {
			let target = left(curIndex);
			if (right(curIndex) < nodeList.length && compare(nodeList[right(curIndex)], nodeList[left(curIndex)])) {
				target = right(curIndex);
			}

			if (compare(nodeList[curIndex], nodeList[target])) {
				break;
			}

			const temp = nodeList[curIndex];
			nodeList[curIndex] = nodeList[target];
			nodeList[target] = temp;
			curIndex = target;
		}
	}

	left(index) {
		return index * 2 + 1;
	}

	right(index) {
		return index * 2 + 2;
	}

	parent(index) {
		return index % 2 === 0 ? index / 2 - 1 : (index - 1) / 2;
	}
}