找到第k大元素
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
来源:力扣(LeetCode) 链接:leetcode.cn/problems/kt… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:把前k大的元素找出来,并在这k个元素中找到最小值
一个无序的数组,第k大元素之前的数据怎么获得?
最小堆
终端节点小于左右节点,具体参考堆排序
根据父节点推断子节点; left = (parent+1)*2-1 right = left + 1
子推断父 parent = (childindex -1)>> 1
/**
* @param {number} k
* @param {number[]} nums
*/
var KthLargest = function (k, nums) {
this.k = k;
//这个最小堆只有k个元素,前k
this.heap = new MinHeap()
for (let item of nums) {
this.add(item)
}
};
/**
* @param {number} val
* @return {number}
*/
KthLargest.prototype.add = function (val) {
this.heap.push(val)
//data长度大于k个元素,就删除堆顶元素
if (this.heap.size() > this.k) {
this.heap.pop()
}
return this.heap.peek()
};
class MinHeap {
constructor() {
this.data = [];
}
size() {
return this.data.length;
}
//最小值
peek() {
return this.size() === 0 ? null : this.data[0]
}
compare(a, b) {
return a - b;
}
swap(a, b) {
[this.data[a], [this.data[b]]] = [this.data[b], [this.data[a]]]
}
shiftUp(item, i) {
let index = i;
while (index > 0) {
//根据当前下标找到父节点进行对比
const parentIndex = (index - 1) >> 1
const parent = this.data[parentIndex]
//如果(子 - 父)返回负数则互换位置
if (this.compare(item, parent) < 0) {
this.swap(index, parentIndex)
index = parentIndex//继续向上调整
} else {
break;
}
}
}
shifDown(item, i) {
let index = i;
//因为本身就是最小堆,只会遍历左边或者右边,另一边是满足条件的
while (index < (this.size() >> 1)) {
//获取左右节点,然后对比
const leftIndex = (index + 1) * 2 - 1
const rightIndex = leftIndex + 1
const left = this.data[leftIndex]
const right = this.data[rightIndex]
//左节点小于父节点
if (this.compare(left, item) < 0) {
//判断左右节点的大小,决定谁和father换
if (rightIndex < this.size() && this.compare(right,left) < 0) {
this.swap(rightIndex,index)
//从右向下调整
index = rightIndex
}else{
this.swap(leftIndex,index)
index = leftIndex
}
}else if(rightIndex < this.size() && this.compare(right,item) < 0){
this.swap(rightIndex,index)
//从右向下调整
index = rightIndex
}else{
break;
}
}
}
push(item) {
this.data.push(item)
//向上调整最小堆
this.shiftUp(item, this.size() - 1)
}
pop() {
if (this.size() === 0) {
return null
}
const last = this.data.pop()
if (this.size() !== 0) {
this.data[0] = last
//删除第0个元素,最后一个元素补上,向下调整最小堆。
this.shifDown(last, 0)
}
}
}
任务调度与最小堆
为了处理优先级高的任务,react定义了俩个任务池
var taskQueue = []//优先级高的
var timerQueue = []
为什么使用使用最小堆? 源码中任务初始结构:
var newTask = {
id:taskIdCouter++ //标记任务的id
callback,
priorityLevel,//任务优先级
startTime,//任务开始时间
expirationTime,//过期时间
sortIndex:-1//任务排序,取值来自过期时间,因此值越小,优先级越高
}
MessageChannel
创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,而一个端口只要绑定了onmessage回调方法,就可以接收从另一个端口传过来的数据。
var channel = new MessageChannel();
var port1 = channel.port1;
var port2 = channel.port2;
port1.onmessage = function(event) {
console.log("port1收到来自port2的数据:" + event.data);
}
port2.onmessage = function(event) {
console.log("port2收到来自port1的数据:" + event.data);
}
port1.postMessage("发送给port2");//port2收到来自port1的数据:发送给port2
port2.postMessage("发送给port1");//port1收到来自port2的数据:发送给port1
/**
*
* MessageChannel的postMessage传递的数据也是深拷贝的,这和web worker的postMessage一样。而且还可以拷贝undefined和循环引用的对象。
*/
window.performance.now()
和 JavaScript 中其他可用的时间类函数(比如Date.now)不同的是,window.performance.now()返回的时间戳没有被限制在一毫秒的精确度内,相反,它们以浮点数的形式表示时间,精度最高可达微秒级。
另外一个不同点是,window.performance.now()是以一个恒定的速率慢慢增加的,它不会受到系统时间的影响(系统时钟可能会被手动调整或被 NTP 等软件篡改)。另外,performance.timing.navigationStart + performance.now() 约等于 Date.now()。
开始编写任务调度代替window.requestIdleCallback
根据newTask的sortIndex来进行最小堆排序,taskQueue就是「k之前元素」的最小堆,拿到堆顶元素就是拿到优先级最高的元素
//返回最小堆堆顶元素,就是优先级最高的任务
function peek(heap) {
return heap.length === 0 ? null : heap[0];
}
//往最小堆中插入元素
function push(heap, node) {
let index = heap.length;
heap.push(node);
shifUp(heap, node, index);//根据当前节点坐标为原数组长度找父
}
//删除堆顶元素
function pop(heap) {
if (heap.length === 0) {
return null;
}
const first = heap[0];
const last = heap.pop();
if (first !== last) {
heap[0] = last;
shifDown(heap, last, 0);
}
return first;
}
//向上调整最小堆
function shifUp(heap, node, i) {
let index = i;
//因为向上调整,需要腹父节点,而0没有父,所以大于0
while (index > 0) {
//父节点下标
const parentIndex = (index - 1) >> 1;
const parent = heap[parentIndex];
//开始比较父节点和子节点,因为push的时候就是一个最小堆,所以插入的元素只要和父节点比较就行
if (compare(parent, node) > 0) {
//父大于子,交换父子
heap[parentIndex] = node;
heap[index] = parent;
//从父节点的位置向上调整
index = parentIndex;
} else {
return;
}
}
}
//向下调整
function shifDown(heap, node, i) {
let index = 0;
let len = heap.length >> 1;
while (index < len) {
const leftIndex = (index + 1) * 2 - 1;
const rightIndex = leftIndex + 1;
const left = heap[leftIndex];
const right = heap[rightIndex];
//开始比较三个节点
if (compare(left, node) < 0) {
//left < parent,left < right??
if (rightIndex < heap.length && compare(right, left) < 0) {
//right最小
heap[index] = right;
heap[rightIndex] = node;
//从右节点向下调整
index = rightIndex;
} else {
//交换left和parent
heap[index] = left;
heap[leftIndex] = node;
//从左节点向下调整
index = leftIndex;
}
//比较right和parent
} else if (rightIndex < heap.length && compare(right, node)) {
heap[index] = right;
heap[rightIndex] = node;
//从右节点向下调整
index = rightIndex;
}else{
//parent最小,本身就是最小堆了,无需向下调整。
return
}
}
}
//比较
function compare(a, b) {
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
export default {
peek,
push,
pop,
}
创建newTask然后进行最小堆排序,使用MessageChannel来确定执行优先级最高的任务
import { getCurrntTime } from "../utils";
import minHeap from "./minHeap";
//立即执行任务
let taskQueue = [];
//用来区分任务
let taskIdCounter = 1;
export function scedulerCallback(callback){
//当前时间
const currentTime = getCurrntTime();
//等多久执行,暂时先全部设为最高优先级
const timeout = -1;
//过期时间
const expirtationTime = currentTime - timeout;
const newTask = {
id:taskIdCounter++ ,//标记任务的id
callback,
// priorityLevel,//任务优先级
// startTime,//任务开始时间
expirtationTime,//过期时间
sortIndex:expirtationTime//任务排序,取值来自过期时间,因此值越小,优先级越高
}
//放入任务池中,开始最小堆
minHeap.push(taskQueue,newTask);
//请求调度
requestHostCallback()
}
function requestHostCallback(){
port.postMessage(null)
}
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = function(){
workloop()
}
function workloop(){
//获取优先级最高的任务
let currentTask = minHeap.peek(taskQueue);
while(currentTask){
const callback = currentTask.callback
//防止重复执行
currentTask.callback = null;
callback()
minHeap.pop(taskQueue);
currentTask =minHeap.peek(taskQueue)
}
}