一般我们说 topK 问题,就可以用大顶堆或小顶堆来实现, 最大的 K 个:小顶堆 最小的 K 个:大顶堆
//构建大顶堆
function MaxHeap(array) {
//开始位置是最后一个非叶子节点
let start = Math.floor(array.length / 2 - 1);
for (let i = start; i >= 0; i--) {
console.log(array);
createHeap(array, i);
}
//打印构造好的大顶堆
console.log(array);
function createHeap(array, index) {
//左子节点
let leftNode = 2 * index + 1;
//右子节点
let rightNode = 2 * index + 2;
let max = index;
//左右子节点与根节点比较,找出最大的数
if (leftNode < array.length && array[leftNode] > array[max]) {
max = leftNode;
}
if (rightNode < array.length && array[rightNode] > array[max]) {
max = rightNode;
}
//交换位置
if (max != index) {
let temp = array[max];
array[max] = array[index];
array[index] = temp;
//交换位置后,不确定该位置下是否还是大顶堆,需要重排
createHeap(array, max);
}
}
}
//构造小顶堆
function MinHeap(array) {
//同样是从最后一个非叶子节点开始处理
let start = Math.floor(array.length / 2 - 1);
//构造小顶堆
for (let i = start; i >= 0; i--) {
createHeap(array, i);
}
console.log(array);
function createHeap(array, index) {
//左叶子节点
let leftNode = 2 * index + 1;
//右叶子节点
let rightNode = 2 * index + 2;
let min = index;
if (leftNode < array.length && array[leftNode] < array[min]) {
min = leftNode;
}
if (rightNode < array.length && array[rightNode] < array[min]) {
min = rightNode;
}
//交换位置
if (min != index) {
let temp = array[min];
array[min] = array[index];
array[index] = temp;
//交换位置之后不确定该min位置是否还是符合小顶堆,需要重排
createHeap(array, min);
}
}
}
/**
* 交换交换根节点和数组末尾元素的值
*/
function adjustHeap(array, arraylen) {
temp = array[0];
array[0] = array[arraylen - 1];
array[arraylen - 1] = temp;
}
/**
* 堆排序 buildBigHeap(建立大顶堆或者小顶堆)
*/
function heapSort(arr) {
len = arr.length;
for (let i = len; i > 0; i--) {
buildBigHeap(arr, i);
adjustHeap(arr, i);
}
}
let findKthLargest = function(nums, k) {
// 从 nums 中取出前 k 个数,构建一个小顶堆
let heap = [,], i = 0
while(i < k) {
heap.push(nums[i++])
}
buildHeap(heap, k)
// 从 k 位开始遍历数组
for(let i = k; i < nums.length; i++) {
if(heap[1] < nums[i]) {
// 替换并堆化
heap[1] = nums[i]
heapify(heap, k, 1)
}
}
// 返回堆顶元素
return heap[1]
};
// 原地建堆,从后往前,自上而下式建小顶堆
let buildHeap = (arr, k) => {
if(k === 1) return
// 从最后一个非叶子节点开始,自上而下式堆化
for(let i = Math.floor(k/2); i>=1 ; i--) {
heapify(arr, k, i)
}
}
// 堆化
let heapify = (arr, k, i) => {
// 自上而下式堆化
while(true) {
let minIndex = i
if(2*i <= k && arr[2*i] < arr[i]) {
minIndex = 2*i
}
if(2*i+1 <= k && arr[2*i+1] < arr[minIndex]) {
minIndex = 2*i+1
}
if(minIndex !== i) {
swap(arr, i, minIndex)
i = minIndex
} else {
break
}
}
}
// 交换
let swap = (arr, i , j) => {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
class Heap {
constructor(comparator = (a, b) => a - b, data = []) {
this.data = data;
this.comparator = comparator;//比较器
this.heapify();//堆化
}
heapify() {
if (this.size() < 2) return;
for (let i = Math.floor(this.size()/2)-1; i >= 0; i--) {
this.bubbleDown(i);//bubbleDown操作
}
}
peek() {
if (this.size() === 0) return null;
return this.data[0];//查看堆顶
}
offer(value) {
this.data.push(value);//加入数组
this.bubbleUp(this.size() - 1);//调整加入的元素在小顶堆中的位置
}
poll() {
if (this.size() === 0) {
return null;
}
const result = this.data[0];
const last = this.data.pop();
if (this.size() !== 0) {
this.data[0] = last;//交换第一个元素和最后一个元素
this.bubbleDown(0);//bubbleDown操作
}
return result;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = (index - 1) >> 1;//父节点的位置
//如果当前元素比父节点的元素小,就交换当前节点和父节点的位置
if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
this.swap(index, parentIndex);//交换自己和父节点的位置
index = parentIndex;//不断向上取父节点进行比较
} else {
break;//如果当前元素比父节点的元素大,不需要处理
}
}
}
bubbleDown(index) {
const lastIndex = this.size() - 1;//最后一个节点的位置
while (true) {
const leftIndex = index * 2 + 1;//左节点的位置
const rightIndex = index * 2 + 2;//右节点的位置
let findIndex = index;//bubbleDown节点的位置
//找出左右节点中value小的节点
if (
leftIndex <= lastIndex &&
this.comparator(this.data[leftIndex], this.data[findIndex]) < 0
) {
findIndex = leftIndex;
}
if (
rightIndex <= lastIndex &&
this.comparator(this.data[rightIndex], this.data[findIndex]) < 0
) {
findIndex = rightIndex;
}
if (index !== findIndex) {
this.swap(index, findIndex);//交换当前元素和左右节点中value小的
index = findIndex;
} else {
break;
}
}
}
swap(index1, index2) {//交换堆中两个元素的位置
[this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
}
size() {
return this.data.length;
}
}
var maxSlidingWindow = function(nums, k) {
let ans = [];
let heap = new Heap((a, b) => b.val - a.val);//大顶堆
for(let i=0;i<k-1;i++) heap.offer({val: nums[i], index: i});//初始的时候将0~k-1的元素加入堆中
for(let i=k-1; i<nums.length; i++){//滑动窗口从从索引为k-1的元素开始遍历
heap.offer({val: nums[i], index: i});//将新进入滑动窗口的元素加堆中
//当堆顶元素不在滑动窗口中的时候,不断删除堆顶堆元素,直到最大值在滑动窗口里。
while(heap.peek().index<=i-k) heap.poll();
ans.push(heap.peek().val);//将在滑动窗口里的最大值加入ans
}
return ans;
}
var maxSlidingWindow = function (nums, k) {
const q = [];//单递减的双端队列
const ans = [];//最后的返回结果
for (let i = 0; i < nums.length; i++) {//循环nums
//当进入滑动窗口的元素大于等于队尾的元素时 不断从队尾出队,
//直到进入滑动窗口的元素小于队尾的元素,以保证单调递减的性质
while (q.length && nums[i] >= nums[q[q.length - 1]]) {
q.pop();
}
q.push(i);//元素的索引入队
while (q[0] <= i - k) {//队头元素已经在滑动窗口外了,移除对头元素
q.shift();
}
//当i大于等于k-1的时候,单调递减队头就是滑动窗口的最大值
if (i >= k - 1) ans.push(nums[q[0]]);
}
return ans;
};