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 <= 10001 <= arr[i] <= 3 * 104arr[0] == 1arr[i]是一个 素数 ,i > 0arr中的所有数字 互不相同 ,且按 严格递增 排序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;
}
}