• 数据结构与算法之堆: Leetcode 215. 数组中的第K个最大元素 (Typescript版)


    数组中的第K个最大元素

    • https://leetcode.cn/problems/kth-largest-element-in-an-array/

    描述

    • 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
    • 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
    • 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

    示例 1

    输入: [3,2,1,5,6,4], k = 2
    输出: 5
    
    • 1
    • 2

    示例 2

    输入: [3,2,3,1,2,4,5,5,6], k = 4
    输出: 4
    
    • 1
    • 2

    提示

    • 1 <= k <= nums.length <= 1 0 5 10^5 105
    • - 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104

    算法实现

    1 )基于js中原生sort api

    const findKthLargest = function(nums, k) {
        return nums.sort((a,b) => b-a)[k - 1]
    };
    
    • 1
    • 2
    • 3
    • 这个浏览器默认提供的sort()方法,一般时间复杂度是 O(nlogn)

    2 )基于堆的数据结构和堆排序的方法

    // 建立最小堆类
    class MinHeap {
        heap: number[] = [];
        // 交换节点位置
        swap(i, j) {
            [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
        }
        // 获得父节点
        getParentIndex(i) {
            return (i - 1) >> 1;
        }
        // 获取左子节点
        getLeftIndex(i) {
            return (i << 1) + 1; // 极客写法
        }
        // 获取右子节点
        getRightIndex(i) {
            return (i << 1) + 2;
        }
        // 向上移动
        shiftUp(index) {
            // 如果到了堆顶元素,index是0,则不要再上移了
            if(!index) return;
            const parentIndex = this.getParentIndex(index)
            if(this.heap[parentIndex] > this.heap[index]) {
                this.swap(parentIndex, index)
                this.shiftUp(parentIndex)
            }
        }
        // 下移
        shiftDown(index) {
            // 边界1:如果到了堆尾元素,则不要再下移了
            if(index >= this.heap.length - 1) return;
            const size = this.size();
            const leftIndex = this.getLeftIndex(index);
            const rightIndex = this.getRightIndex(index);
            if (leftIndex < size && this.heap[leftIndex] < this.heap[index]) {
                this.swap(leftIndex, index);
                this.shiftDown(leftIndex);
            }
            if (rightIndex < size && this.heap[rightIndex] < this.heap[index]) {
                this.swap(rightIndex, index);
                this.shiftDown(rightIndex);
            }
        }
        // 插入
        insert(value) {
            this.heap.push(value);
            this.shiftUp(this.heap.length - 1);
        }
        // 删除堆顶
        pop() {
            // pop()方法删除数组最后一个元素并返回,赋值给堆顶
            this.heap[0] = this.heap.pop();
            // 对堆顶重新排序
            this.shiftDown(0);
        }
        // 获取堆顶
        peak() {
            return this.heap[0];
        }
        // 获取堆的大小
        size() {
            return this.heap.length;
        }
    }
    
    // 实现
    const findKthLargest = (nums, k) => {
        const h = new MinHeap();
        nums.forEach(n => {
            // 将数组元素依次插入堆中
            h.insert(n);
            // 如果堆满,则执行优胜劣汰
            (h.size() > k) && h.pop();
        })
        // 返回堆顶,此时就是第k大的元素
        return h.peak();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 关键在于这个堆的数据结构提供的 insert 方法 与 pop 方法
    • 时间复杂度:O(nlogk)
      • 一个n循环,里面还嵌套一个heap的上移递归操作logk
      • 总体:n*logk
    • 空间复杂度: O(k) 或 O(logn)
      • 堆的大小,数组的大小, k是输入的堆大小
    • 注意
      • 本题使用的是一个堆排序的算法,O(nlogn)
      • 但是还有其他排序也可以达到这个效率
      • 但是这个不符合题目的要求:时间复杂度为 O(n) 的算法

    3 )基于冒泡排序

    function findKthLargest(nums: number[], k: number): number {
        const len = nums.length - 1;
        for (let i = len; i > len - k; i--) {
            for (let j = 0; j < i; j++) {
                if (nums[j] > nums[j + 1]) {
                    [nums[j], nums[j + 1]] = [nums[j + 1], nums[j]];
                }
            }
        }
        return nums[len - k + 1];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 这个算法基于冒泡排序修改而来,但是无法通过 leetcode , 对于一些用例数据,超出时间限制

    4 )基于快速排序

    function quickselect(nums: number[], l: number, r: number, k: number) {
            if (l == r) return nums[k];
            const pivot = nums[l];
            let i:number = l - 1, j:number = r + 1;
            while (i < j) {
                do i++; while (nums[i] < pivot);
                do j--; while (nums[j] > pivot);
                if (i < j) [nums[i], nums[j]] = [nums[j], nums[i]];
            }
            return k <= j ? quickselect(nums, l, j, k) : quickselect(nums, j + 1, r, k);
        }
    
    function findKthLargest(nums: number[], k: number): number {
        const n = nums.length;
        return quickselect(nums, 0, n - 1, n - k);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 这是官方提供的方法
    • 时间复杂度 O(n), 证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」。
    • 空间复杂度 O(logn),递归使用栈空间的空间代价的期望为 O(log⁡n)O(\log n)O(logn)。
  • 相关阅读:
    【hexo】butterfly主题魔改之天气插件
    lap库就该这么安装
    视频目标语义分割自动标注——从图像轮廓提取到转成json标签文件
    Pandas 速查手册
    【牛客-剑指offer-数据结构篇】【图解】JZ79 判断是不是平衡二叉树 Java实现
    【java8】函数式接口
    QT基础入门【QSS】QT伪状态类型和实例
    国货之光——jdchain1.6.5测试网络部署
    WPF ListCollectionView排序、过滤和分组
    C++DAY10 结构体·定义与使用
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/133471440