这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
108. 数据流中的第 K 大元素 (kth-largest-element-in-a-stream)
标签
- 堆
- 优先级队列
- 简单(针对有优先队列 api 的语言)
题目
这里不贴题了,leetcode打开就行,题目大意:
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]
解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3); // return 4
kthLargest.add(5); // return 5
kthLargest.add(10); // return 5
kthLargest.add(9); // return 8
kthLargest.add(4); // return 8
基本思路
这个问题用其他语言实现比较简单,js 的话要绕一点,用优先级队列思想,其中一种实现方式就是堆
因为数据流是变化的,会有数加进来,但我们始终关心前 k 大的数,那么建立一个小顶堆,堆顶就是第 k 个大的数,我们只需要维护这个堆(长度为 k)即可。
add 方法的关键:
- 当 heap 数组长度
小于 k时,新数从数组末尾推入,上浮跟父元素比较,交换到它合适的位置。 - 当 heap 数组长度
大于等于 k时,如果新数字大于栈顶元素,用它替换堆顶,下沉跟左右孩子比较,最终交换到合适的位置。
最后这个堆存的是前 k 大的数字,堆顶是第 k 大的数字,因为是小顶堆,也就是这个堆的最小值。
写法实现
class MinHeap {
constructor(k, nums) {
// 优先级队列数组,heap 是个 二叉小顶堆 为了计算方便
this.heap = [];
this.k = k;
}
// 堆的插入
add(val) {
// 最小堆没满,先入堆
if (this.heap.length < this.k) {
// 入堆后在数组末尾 index = this.heap.length - 1
this.heap.push(val)
// 上浮操作,让小顶堆成立
this.up(this.heap.length - 1)
} else if (val > this.heap[0]) {
// 如果这个元素比堆顶大,就把顶换掉,因为顶原来是第 k 个大的,
// 现在比他大的来了一个,他就排到第 k + 1个大的了,被挤出了这个小顶堆
this.heap[0] = val
this.down(0)
}
// 否则直接丢弃这个元素,因为它比最小堆顶还小,说明入不了前 k 个大的元素了
}
// 由底比较 上浮操作
up(idx) {
// 因为是二叉堆,计算出堆的父节点的 index = parent
let parent = Math.floor((idx - 1) / 2);
// 当发现 父节点大于 idx 节点 上浮
if (parent >= 0 && this.heap[parent] > this.heap[idx]) {
// 交换 parent 和 idx 位置
[this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
// 继续递归上浮
this.up(parent);
}
}
// 由顶比较 下沉操作
down(idx) {
let tempIdx = idx;
// 左孩子 idx = idx * 2 + 1
let left = idx * 2 + 1;
// 比较左孩子,如果发现比左孩子大,换位置
if (left < this.heap.length && this.heap[left] < this.heap[tempIdx]) {
tempIdx = left;
}
let right = idx * 2 + 2;
// 右边同理
if (right < this.heap.length && this.heap[right] < this.heap[tempIdx]) {
tempIdx = right;
}
// tempIdx 现在是左右孩子中小的那个,或者都比根大(tempIdx 仍然为 idx) 如果有改动,则交换节点位置
if (tempIdx !== idx) {
[this.heap[tempIdx], this.heap[idx]] = [this.heap[idx], this.heap[tempIdx]];
// 继续递归
this.down(tempIdx);
}
}
}
// 声明全局变量
let minHeap = []
var KthLargest = function (k, nums) {
// 建立一个小顶堆 赋给全局变量
minHeap = new MinHeap(k, nums);
// 所有元素都添加到最小堆
for (let i = 0; i < nums.length; i++) {
minHeap.add(nums[i]);
}
};
KthLargest.prototype.add = function (val) {
minHeap.add(val);
// 返回堆顶元素,就是第 k 大的元素
return minHeap.heap[0];
};
let kthLargest = new KthLargest(3, [8, 4, 5, 2]);
console.log(kthLargest.add(3)) // return 4
console.log(kthLargest.add(5)) // return 5
console.log(kthLargest.add(10)) // return 5
console.log(kthLargest.add(9)) // return 8
console.log(kthLargest.add(4)) // return 8
另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我
presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧