数据流中的第K大元素
LeetCode传送门703. 数据流中的第 K 大元素
题目
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.
Implement KthLargest class:
KthLargest(int k, int[] nums) Initializes the object with the integer k and the stream of integers nums. int add(int val) Appends the integer val to the stream and returns the element representing the kth largest element in the stream.
Example:
Input
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
Output
[null, 4, 5, 5, 8, 8]
Explanation
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
Constraints:
At most calls will be made to add. It is guaranteed that there will be at least k elements in the array when you search for the kth element.
思考线
解题
屌丝版
看到这个题,首先我是不慌的,不就是排序嘛,简单。JS数组中sort方法可解此题。于是我飘飘然写下如下解答方案
/**
* @param {number} k
* @param {number[]} nums
*/
var KthLargest = function (k, nums) {
this.k = k;
this.nums = nums;
}
/**
* @param {number} val
* @return {number}
*/
KthLargest.prototype.add = function (val) {
this.nums.push(val);
this.nums.sort((a,b) => b - a);
return this.nums[this.k -1]
}
点击提交,是可以通过测试用例的。但是如果让我们的nums的范围扩大,使用上面的方法还能行吗?
我们分析一下sort方法是基于比较排序的。所有基于比较的排序都有个极限时间复杂度nlog(n)。而我们每次add的时候都要执行一次sort方法,那么我们的时间复杂度是k*nlog(n)。有没有什么更好的算法来解这个题呢?
答案是有的。在之前我们学过了堆排序。
试想一下
我们如果只维护一个长度为k的小顶堆。
每次add 的时候,判断小顶堆内的长度是不是达到了K个。
若没达到,直接把值放到小顶堆内
若达到,看小顶堆顶部的元素是不是比将要放入的元素大 若是比将要放入的元素大,则不处理 若比将要放入的元素小,则删除堆顶元素,同时把add的元素放入小顶堆。
这样一来我们的时间每次操作 add的时候时间复杂度就变成了log(k),这样一来我们的时间复杂度就大大降低了。
现在唯一的问题是如何构建一个小顶堆呢?
我们知道js是没有提供堆这种数据结构给我们使用的,那么我们就自己手动构建一个吧。
关于如何构建我再掘金和B站都有详细的文章和视频。
大家可以根据自己的情况来阅读。
接下来我直接放代码
/**
* @param {number} k
* @param {number[]} nums
*/
var KthLargest = function (k, nums) {
this.k = k;
this.minHeap = new MinHeap(); // 只需要维护长度为k的小顶堆
for(let i = 0; i < nums.length; i ++) { // loop n次
this.add(nums[i]); // lg
}
console.log(this.minHeap.data)
// O(nlgn) O(lgn) ==> k
};
/**
* @param {number} val
* @return {number}
*/
KthLargest.prototype.add = function (val) {
if(this.minHeap.size() < this.k) {
this.minHeap.offer(val);
} else {
const peak = this.minHeap.peek();
if(peak < val) {
this.minHeap.setOne(val);
}
}
return this.minHeap.peek()
};
class MinHeap {
constructor(data = []) {
this.data = data;
this.comporator = (a, b) => a - b;
this.heapify();
}
heapify() {
// 构建小顶堆
if (this.size < 2) return;
for (let i = 1; i < this.size() - 1; i++) {
this.bubbleUp(i);
}
}
setOne(val) {
this.data[0] = val;
this.bubbleDown(0); // lgn
}
// 返回小顶堆的长度
size() {
return this.data.length;
}
// 查看堆顶的元素
peek() {
return this.data[0]
}
// 取出堆顶元素
poll() {
const res = this.data.shift();
this.data.size() && this.data.unshift(this.data.pop());
this.bubbleDown(0);
return res;
}
offer(val) {
this.data.push(val);
this.bubbleUp(this.size() -1); // lgn
}
// 向上冒泡,让堆保证为小顶堆
bubbleUp(index) {
while (index) {
const parent = (index - 1) >> 1;
if (this.comporator(this.data[parent], this.data[index]) > 0) {
this.swap(index, parent);
index = parent;
} else {
break;
}
}
}
// 向下冒泡,让堆保证为小顶堆
bubbleDown(index) {
const lastIndex = this.size() - 1;
while (index < lastIndex) {
const left = index * 2 + 1;
const right = index * 2 + 2;
let findIndex = index
if (left <= lastIndex && this.comporator(this.data[findIndex], this.data[left]) > 0) {
findIndex = left;
}
if (right <= lastIndex && this.comporator(this.data[findIndex], this.data[right]) > 0) {
findIndex = right;
}
if(findIndex !== index) {
this.swap(index, findIndex);
index = findIndex;
} else {
break
}
}
}
swap(index1, index2) {
const temp = this.data[index1];
this.data[index1] = this.data[index2];
this.data[index2] = temp;
}
}
这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。