LeetCode Hot100(54/100)——215. 数组中的第K个最大元素

文章目录

一、题目说明

题目链接LeetCode - 数组中的第 K 个最大元素

题目描述:

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

注意: 这是指排序后的第 k 个最大元素,而不是第 k 个不同的元素。

示例:

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

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

二、思维导图

下面是本题常见四种解法的思考路线:
第 K 个最大元素
排序法
时间复杂度 O(n log n)
直接排序取第 k 个
堆法(优先队列)
小顶堆保存前 k 大元素
时间复杂度 O(n log k)
空间复杂度 O(k)
快速选择法 (Quick Select)
基于快速排序思想
平均复杂度 O(n)
最坏复杂度 O(n^2)
冒泡法思路 (优化较差)
重复扫描取最大值
时间复杂度 O(k * n)
不推荐


三、解法一:排序法(暴力思路)

原理

最直接的方式------先排序,再取第 k 个最大值。

将数组从大到小排序后,取下标为 k-1 的元素即可。

流程图

输入数组 nums 和 k
对 nums 进行降序排序
返回 nums[k-1]

代码实现(Java)

java 复制代码
public class Solution {
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }
}

复杂度分析

  • 时间复杂度: O(n log n)
    排序需要 O(n log n)
  • 空间复杂度: O(1)
    若使用原地排序,则不额外占用空间。

优缺点:

  • ✅ 简单易实现
  • ❌ 对大规模数据性能较差

四、解法二:堆排序思路(小顶堆法)

原理

可以用 最小堆(Min Heap) 来维护当前前 k 大的元素:

  • 遍历数组,将元素加入堆中;
  • 每当堆大小超过 k,就弹出最小值;
  • 遍历完毕后,堆顶即是第 k 个最大元素。

思维示意



初始:堆为空
逐步插入元素
堆大小 > k?
弹出堆顶最小
继续插入
遍历完成 -> 堆顶即为第K大

代码实现(Java)

java 复制代码
import java.util.PriorityQueue;

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>(); // 小顶堆
        for (int n : nums) {
            heap.offer(n);
            if (heap.size() > k) {
                heap.poll(); // 弹出堆顶最小元素
            }
        }
        return heap.peek(); // 堆顶就是第k大元素
    }
}

复杂度分析

  • 时间复杂度: O(n log k)
    每次插入和可能的弹出操作都是 O(log k)
  • 空间复杂度: O(k)

优缺点:

  • ✅ 性能好,适合大数据
  • ✅ 可用于流式数据场景(可维护前 k 大元素)
  • ❌ 实现稍复杂

五、解法三:快速选择法(Quick Select)

原理

该方法基于快速排序的"分区(Partition)"思想:

  • 选定一个基准(pivot),将数组分为两部分:
    • 左侧大于 pivot 的元素
    • 右侧小于 pivot 的元素
  • 根据 pivot 的位置,判断是否正好是第 k 大:
    • 如果 pivotIndex == k,直接返回;
    • 否则在相应区间递归查找。

时序图

pivot选取 分区操作 当前数组 pivot选取 分区操作 当前数组 执行 partition(nums) 选取 pivot,调整左右区域 返回 pivotIndex 判断 pivotIndex 与 k 的关系

代码实现(Java)

java 复制代码
import java.util.Random;

public class Solution {
    public int findKthLargest(int[] nums, int k) {
        int target = nums.length - k;
        return quickSelect(nums, 0, nums.length - 1, target);
    }

    private int quickSelect(int[] nums, int left, int right, int k) {
        if (left == right) return nums[left];

        int pivotIndex = partition(nums, left, right);
        if (pivotIndex == k) {
            return nums[pivotIndex];
        } else if (pivotIndex < k) {
            return quickSelect(nums, pivotIndex + 1, right, k);
        } else {
            return quickSelect(nums, left, pivotIndex - 1, k);
        }
    }

    private int partition(int[] nums, int left, int right) {
        int pivot = nums[right];
        int i = left;
        for (int j = left; j < right; j++) {
            if (nums[j] <= pivot) {
                swap(nums, i, j);
                i++;
            }
        }
        swap(nums, i, right);
        return i;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

复杂度分析

  • 平均时间复杂度: O(n)
  • 最坏情况: O(n²)
  • 空间复杂度: O(1)

优缺点:

  • ✅ 平均性能优秀
  • ✅ 不需要额外空间
  • ❌ 最坏情况下可能退化为 O(n²)

六、不同解法对比表

解法类型 平均时间复杂度 空间复杂度 适合场景 备注
排序法 O(n log n) O(1) 小数据量 简单易实现
堆法 O(n log k) O(k) 大数据或流式数据 常用方案
快速选择法 O(n) O(1) 通用高性能场景 平均最优

  • 若是面试题,推荐使用 堆法快速选择法
  • 排序法虽简单,但效率一般;
  • 快速选择法在平均情况下非常快,是最优选择之一;
  • 小顶堆适合处理实时的前 k 大问题,例如实时数据流。
相关推荐
We་ct1 小时前
LeetCode 92. 反转链表II :题解与思路解析
前端·算法·leetcode·链表·typescript
春日见1 小时前
如何查看我一共commit了多少个,是哪几个,如何回退到某一个版本
vscode·算法·docker·容器·自动驾驶
uesowys2 小时前
华为OD算法开发指导-二级索引-Read and Write Path Different Version
java·算法·华为od
TracyCoder1232 小时前
LeetCode Hot100(55/100)——347. 前 K 个高频元素
数据结构·算法·leetcode
码农三叔2 小时前
(11-4-03)完整人形机器人的设计与实现案例:盲踩障碍物
人工智能·算法·机器人·人机交互·人形机器人
Wect2 小时前
LeetCode 92. 反转链表II :题解与思路解析
前端·算法·typescript
Wect2 小时前
LeetCode 138. 随机链表的复制:两种最优解法详解
前端·算法·typescript
近津薪荼2 小时前
优选算法——前缀和(4):除了自身以外数组的乘积
算法
李派森2 小时前
AI大模型之丙午马年运势模型的构建与求解
笔记·算法