215. 数组中的第 K 个最大元素(Kth Largest Element in an Array)

频次 ★★★★★ · 难度 🟡 · 高频:全厂

题目

整数数组 nums 和整数 k,返回数组中第 k 个最大元素(非第 k 个不同元素)。

思路

两种主流解法

解法 1:快速选择(Quick Select) — 快排的 partition 思想,期望 O(n)。随机选 pivot 后比它大的放左边,比它小的放右边,判断第 k 个最大在哪边递归。

解法 2:大小为 k 的最小堆 — 维护一个大小为 k 的最小堆,遍历数组时如果堆大小 < k 直接入堆,否则如果当前元素 > 堆顶,弹出堆顶后入堆。遍历结束堆顶就是第 k 大。O(n log k)。

面试中推荐先答堆解法(稳定 O(n log k)),再答快速选择(平均 O(n) 但最坏 O(n²))。

代码

// 堆解法
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>();  // 最小堆
    for (int n : nums) {
        pq.offer(n);
        if (pq.size() > k) pq.poll();
    }
    return pq.peek();
}
// 快速选择
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 idx = partition(nums, l, r);
    int rank = idx - l + 1;                    // nums[idx] 是第 rank 大
    if (rank == k) return nums[idx];
    if (rank < k) return quickSelect(nums, idx + 1, r, k - rank);
    return quickSelect(nums, l, idx - 1, 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 t = nums[i]; nums[i] = nums[j]; nums[j] = t;
}

复杂度

  • :时间 O(n log k),空间 O(k)
  • 快速选择:平均 O(n),最坏 O(n²),空间 O(log n)

边界条件

  • k = 1:最大值
  • k = n:最小值
  • 含重复值:正常处理(不计”不同”)

变式

易错点

  • 第 k 不是第 k 个不同元素,重复值分别计数
  • 堆解法用最小堆:堆顶是堆中最小元素,堆中保留最大的 k 个,堆顶就是第 k 大
  • 快速选择 partition 的边界条件小心递归偏移(k - rank

面试追问

  • 为什么堆解法不直接用最大堆? 最大堆需要保留全部 n 个元素,O(n log n)。最小堆只保留 k 个,O(n log k),k 远小于 n 时优势明显

关联题