[路飞]_每天刷leetcode_26(数据流中的第K大元素 Kth Largest Element in a Stream)

598 阅读3分钟

数据流中的第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:

1<=k<=1040<=nums.length<=104104<=nums[i]<=104104<=val<=1041 <= k <= 10^4\\ 0 <= nums.length <= 10^4\\ -10^4 <= nums[i] <= 10^4\\ -10^4 <= val <= 10^4\\

At most 10410^4 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站都有详细的文章和视频。

大家可以根据自己的情况来阅读。

掘金文章:堆排序

B站视频:堆排序

B站视频:数据流中的第K大元素

接下来我直接放代码

/**
 * @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;
    }

}

这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。