[路飞]_数据流中的第 K 大元素

218 阅读4分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

leetcode-703 数据流中的第 K 大元素
b站视频

题目介绍

设计一个找到数据流中第 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

提示:

  • 1 <= k <= 10^4
  • 0 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • -10^4 <= val <= 10^4
  • 最多调用 add 方法 10^4 次 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

解题思路

这道题可以转化为求最值的问题,数据流中的第 K 大元素,那么我们可以将其转化为最大的 K 个元素中的最小值,转化为最值问题之后就可以用堆的思想来解决

但是今天不想直接从堆这种解决方式开始将,我们先从最直接的暴力排序讲起,然后一步一步优化成堆的结构

思路一:暴力排序

暴力排序的方式就是每次调用 add 方法的时候,直接将 valpush 到数组中,然后对数组进行排序,并返回第 k 大的值

解题代码

var KthLargest = function(k, nums) {
    this.arr = nums
    this.k = k
};

KthLargest.prototype.add = function(val) {
    this.arr.push(val)
    this.arr.sort((a, b) => a - b)
    return this.arr[this.arr.length - this.k]
};

这种方法的缺点是,每次调用 add 方法插入值之后,整个数组的长度会越来越长,按题目所说,最多调用 add 方法 10^4 次,对这么长的数组就行多次排序是极其耗费性能的,我们可以用第二种方法来进行优化

思路二:维护最大 K 个数

因为题目要求的是返回第 k 大元素,所以我们只需要关注前 k 大的元素就可以了,后面多的元素我们可以直接丢掉,这样不需要每次都对这些没用的数据进行排序,只需要对前 k 个值进行排序就可以了

解题步骤

  1. nums 从大到小进行排序
  2. 截取排序后 nums 的前 k
  3. 往数组中插入数据前进行判断:
  • 如果数组的长度小于 k 值,直接向数组中插入该值,然后对数组从大到小进行排序,返回数组的最后一个元素;
  • 如果数组的长度等于 k 值,判断要插入的值是否大于数组的最后一个元素,如果是,将数组最后一个元素替换为插入的值,然后对数组从大到小进行排序,并返回数组的最后一个元素;如果不是,则不进行任何操作,直接返回数组的最后一个元素

数据流中的第 K 大元素.gif

解题代码

var KthLargest = function(k, nums) {
    nums.sort((a, b) => b - a)
    this.arr = nums.slice(0, k)
    this.k = k
};

KthLargest.prototype.add = function(val) {
    if (this.arr.length < this.k) {
        this.arr.push(val)
        this.arr.sort((a, b) => b - a)
    } else if (val > this.arr[this.k - 1]) {
        this.arr[this.k - 1] = val
        this.arr.sort((a, b) => b - a)
    }
    return this.arr[this.k - 1]
};

这种方法虽然不需要对所有插入的元素进行排序,当还是需要对这 k 个元素进行排序,为什么堆的效率可以比这种方法高呢?因为堆同样也是维护 k 个元素,但是堆不需要对所有元素进行排序,每次只是父节点与左节点或者右节点进行比较,因此至少有一半的元素是不需要进行比较的。

思路三:小顶堆

小顶堆同样是维护最大的 k 个元素,堆顶元素最小,也就是第 k 大的元素,因此我们可以直接返回堆顶元素即可

本题的堆需要有 push(插入元素)、size(堆的大小)、top(返回堆顶元素,第 k 大的元素)、sortBack(向上调整堆结构)、sortFront(向下调整堆结构)五个方法

解题步骤

  1. 创建一个大小为 k 的小顶堆
  2. 依次向堆中插入元素,并调整堆结构
  3. 如果要插入时,堆中元素的数量小于 k,则插入到堆尾,然后向上调整堆的结构
  4. 如果插入时,堆中元素的数量为 k,则比较要插入元素和堆顶元素的大小,如果堆顶元素大,则不做操作,如果堆顶元素小,则将堆顶元素替换为插入的值,然后向下调整堆结构
  5. 返回堆顶元素的值

数据流中的第 K 大元素-小顶堆.gif

解题代码

var KthLargest = function(k, nums) {
    this.heap = new Heap(k)
    while (nums.length) {
        this.heap.push(nums.pop())
    }
};

KthLargest.prototype.add = function(val) {
    this.heap.push(val)
    return this.heap.top()
};

class Heap {
    constructor(k) {
        this.arr = []
        this.k = k
    }

    // 返回当前堆的大小
    size() {
        return this.arr.length
    }

    // 返回堆顶元素
    top() {
        return this.arr[0]
    }

    // 向堆中插入元素
    push(val) {
        if (this.size() < this.k) {
            this.arr.push(val)
            this.sortBack()
        } else if (val > this.arr[0]) {
            this.arr[0] = val
            this.sortFront()
        }
    }

    // 向上调整
    sortBack() {
        let i = this.arr.length - 1
        while (i > 0 && this.arr[i] < this.arr[Math.floor((i - 1) / 2)]) {
            [this.arr[i], this.arr[Math.floor((i - 1) / 2)]] = [this.arr[Math.floor((i - 1) / 2)], this.arr[i]]
            i = Math.floor((i - 1) / 2)
        }
    }
    // 向下调整
    sortFront() {
        let i = 0 
        while (i * 2 + 1 < this.size()) {
            let temp = i
            if (this.arr[i * 2 + 1] < this.arr[temp]) temp = i * 2 + 1
            if (this.arr[i * 2 + 2] !== undefined && this.arr[i * 2 + 2] < this.arr[temp]) temp = i * 2 + 2
            if (i === temp) break
            [this.arr[i], this.arr[temp]] = [this.arr[temp], this.arr[i]]
            i = temp
        }
    }
}