🔥 LeetCode 215:数组中的第 K 大元素 —— 从暴力排序到快速选择,TopK 问题的三种解法全解析

10 阅读3分钟

关键词:TopK / 排序 / 堆 / 快速选择
核心思想:在正确的地方,用合适的数据结构和算法


一、题目回顾

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

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

示例:

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

二、解题思路总览(先立大局)

这是一个经典的 TopK 问题,常见解法有三类:

方法思想适合场景
排序全局有序简单直接
维护 TopK工程常用
快速选择局部排序算法最优

下面我们按 由浅入深 的顺序逐一分析。


三、方法一:直接排序(最直观)

思路

  1. 对数组排序
  2. 返回下标为 nums.length - k 的元素

代码示例

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

复杂度分析

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)(原地排序)

优缺点分析

优点

  • 思路简单
  • 代码最少
  • 适合快速写对

缺点

  • 排序了“所有元素”,而我们只关心一个
  • 不满足进阶要求

👉 更像“新手解法”,但在面试里能保底


四、方法二:堆(工程向最优解)

核心思想

用一个 大小为 k 的小顶堆
始终维护数组中最大的 k 个元素

为什么是小顶堆?

  • 堆顶 = 当前第 k 大
  • 新元素更大 → 顶掉堆顶
  • 小元素 → 自动淘汰

代码示例(小顶堆)

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        for (int num : nums) {
            pq.add(num);
            if (pq.size() > k) {
                pq.poll();
            }
        }
        return pq.peek();
    }
}

复杂度分析

  • 时间复杂度:O(n log k)
  • 空间复杂度:O(k)

优缺点分析

优点

  • 不需要排序全部数据
  • 稳定、好写、好理解
  • 实际工程中非常常见

缺点

  • 需要额外空间
  • 时间复杂度不是理论最优

👉 这是“工程友好型解法”


五、方法三:快速选择(算法天花板)

核心思想(一句话)

利用快速排序的分区思想,每次只递归一边

这不是完整的快速排序,而是:

Quick Select(快速选择)


关键认知升级

  • 排序是“把所有人排好”

  • 快速选择是:

    • 每次确定一个元素的最终位置
    • 如果不是目标 → 丢掉一半空间

代码示例(随机化 Quick Select)

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

    private int quickSelect(int[] nums, int l, int r, int k) {
        int pivotIndex = partition(nums, l, r);
        if (pivotIndex == k - 1) {
            return nums[pivotIndex];
        } else if (pivotIndex > k - 1) {
            return quickSelect(nums, l, pivotIndex - 1, k);
        } else {
            return quickSelect(nums, pivotIndex + 1, r, k);
        }
    }

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

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

复杂度分析

情况时间复杂度
平均O(n)
最坏O(n²)(随机化可规避)

空间复杂度:O(1)


优缺点分析

优点

  • 理论最优
  • 原地算法
  • 面试官非常喜欢

缺点

  • 实现复杂
  • 边界容易出错
  • 不适合临场硬写

👉 这是“算法能力的分水岭解法”


六、三种方法对比总结

方法时间复杂度空间特点推荐指数
排序O(n log n)O(1)最简单⭐⭐
小顶堆O(n log k)O(k)工程常用⭐⭐⭐⭐
快速选择O(n)O(1)算法最优⭐⭐⭐⭐⭐

七、选哪种?

  • 写得快、保正确 → 排序
  • 工程实践 / 数据流 / 大数据 → 堆
  • 追求最优复杂度 / 算法面试 → 快速选择

八、一句话总结

TopK 问题不是只会一种解法,
而是根据场景,
在“简单、稳定、最优”之间做权衡。