(leetcode)力扣100 74 数组中的第K个最大元素(快速选择\堆)

题目

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

数据范围

1 <= k <= nums.length <= 105

-104 <= nums[i] <= 104

测试用例

示例1

java 复制代码
输入: [3,2,1,5,6,4], k = 2
输出: 5

示例2

java 复制代码
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

题解1(官解1 ,时间On,空间Ologn)

java 复制代码
class Solution {
    // 快速选择核心函数:在 [l, r] 的区间内,寻找最终排好序后索引为 k 的元素
    int quickselect(int[] nums, int l, int r, int k) {
        // 递归终止条件:如果区间内只有一个元素,说明找到了目标,直接返回
        if (l == r) return nums[k];
        
        // 选取基准值(Pivot),这里简单地取了区间最左侧的元素
        int x = nums[l];
        // 初始化双指针,i 在区间左侧的前一个位置,j 在区间右侧的后一个位置
        // 这是为了配合后面的 do-while 循环
        int i = l - 1, j = r + 1;
        
        // 开始 Hoare 分区操作
        while (i < j) {
            // i 指针向右移动,直到找到一个大于等于基准值 x 的数
            do i++; while (nums[i] < x);
            // j 指针向左移动,直到找到一个小于等于基准值 x 的数
            do j--; while (nums[j] > x);
            
            // 如果两个指针还没有相遇,说明 nums[i] 放错了左边,nums[j] 放错了右边
            // 交换它们,让小于基准值的去左边,大于基准值的去右边
            if (i < j){
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }
        
        // 分区结束后,整个区间被分成了两部分:[l, j] 和 [j+1, r]
        // 并且满足:[l, j] 中的所有元素 <= [j+1, r] 中的所有元素
        
        // 判断目标索引 k 落在哪一半
        if (k <= j) {
            // 如果 k 在左半部分,递归左半区
            return quickselect(nums, l, j, k);
        } else {
            // 如果 k 在右半部分,递归右半区
            return quickselect(nums, j + 1, r, k);
        }
    }
    
    public int findKthLargest(int[] _nums, int k) {
        int n = _nums.length;
        // 问题转换:求"第 k 大"的元素,等价于求升序排列后的"第 n-k 小"的元素
        // 它的索引也就是 n-k。例如长度为 5 的数组,第 1 大的元素,排好序后索引是 5-1=4。
        return quickselect(_nums, 0, n - 1, n - k);
    }
}

题解2(官解2,堆排序)

java 复制代码
class Solution {
    // 主函数:寻找数组中的第 k 个最大元素
    public int findKthLargest(int[] nums, int k) {
        int heapSize = nums.length;
        // 步骤 1:将整个数组构建成一个大根堆
        buildMaxHeap(nums, heapSize);
        
        // 步骤 2:执行 k-1 次删除堆顶元素的操作
        // i 表示当前要和堆顶交换的末尾位置
        for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
            // 将当前堆顶(最大值)交换到数组末尾,相当于把它从堆中"移除"并存起来
            swap(nums, 0, i);
            // 堆的大小减 1,刚才换到末尾的最大值不再参与后续的堆调整
            --heapSize;
            // 因为新的堆顶元素可能是个较小的值,破坏了堆的性质,所以需要从堆顶(索引 0)向下重新调整堆
            maxHeapify(nums, 0, heapSize);
        }
        // 经过 k-1 次交换和调整后,此时的堆顶 nums[0] 就是第 k 大的元素
        return nums[0];
    }

    // 建堆函数:从最后一个非叶子节点开始,逆序向上依次调整
    public void buildMaxHeap(int[] a, int heapSize) {
        // heapSize / 2 - 1 是完全二叉树中最后一个非叶子节点的索引
        for (int i = heapSize / 2 - 1; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    // 堆化函数(核心):负责维护大根堆的性质(父节点的值 >= 子节点的值)
    // 作用是将索引 i 处的元素"下沉"到合适的位置
    public void maxHeapify(int[] a, int i, int heapSize) {
        // 在数组表示的完全二叉树中,节点 i 的左孩子索引是 2*i + 1,右孩子是 2*i + 2
        int l = i * 2 + 1, r = i * 2 + 2;
        // largest 记录当前节点及其左右孩子中,值最大的那个节点的索引
        int largest = i;
        
        // 如果左孩子在堆的范围内,且左孩子的值大于当前最大值,更新 largest
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        // 如果右孩子在堆的范围内,且右孩子的值大于当前最大值,更新 largest
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        
        // 如果最大值不是当前的父节点 i,说明破坏了堆的性质
        if (largest != i) {
            // 将父节点和最大的孩子交换
            swap(a, i, largest);
            // 交换后,原来的父节点落到了 largest 的位置,可能会继续破坏下一层的堆性质
            // 所以对 largest 位置递归调用 maxHeapify 继续向下调整(下沉)
            maxHeapify(a, largest, heapSize);
        }
    }

    // 辅助函数:交换数组中的两个元素
    public void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

思路

这道题如果严格要求时间复杂度on的话,还是比较考察积累的,我们都知道快速排序是onlogn的时间复杂度,但快速选择的时间复杂度on,

理解这个之后,再把这个方法积累起来就可以了,因为实现并不难。

第二个方法堆排序时间复杂度是onlogn,但有些面试官要考察堆排序(手写),所以这里大家一定要学会其对应写法,并不难,积累即可。

相关推荐
云深处@1 小时前
【数据结构】排序
数据结构·算法·排序算法
舟舟亢亢10 小时前
算法总结——二叉树【hot100】(上)
java·开发语言·算法
weixin_4772716911 小时前
根象:树根。基石。基于马王堆帛书《周易》原文及甲骨文还原周朝生活活动现象(《函谷门》原创)
算法·图搜索算法
普通网友11 小时前
多协议网络库设计
开发语言·c++·算法
努力努力再努力wz11 小时前
【Linux网络系列】:TCP 的秩序与策略:揭秘传输层如何从不可靠的网络中构建绝对可靠的通信信道
java·linux·开发语言·数据结构·c++·python·算法
daxi15012 小时前
C语言从入门到进阶——第9讲:函数递归
c语言·开发语言·c++·算法·蓝桥杯
持续学习的程序员+113 小时前
强化学习Q-chunking算法
算法
Polaris北13 小时前
第二十七天打卡
开发语言·c++·算法
风吹乱了我的头发~13 小时前
Day30:2026年2月20日打卡
算法