跳到正文
第 215 题中等

数组中的第K个最大元素

Kth Largest Element in an Array

返回数组中第 k 大的元素(按大小排序后的第 k 个,不是第 k 个不同值)。

在力扣中练习

题目描述

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

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

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

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

解题思路

主解法

小顶堆 · 保留最大的 k 个

堆里始终只有 k 个数,堆顶就是第 k 大

要找第 k 大,不需要把所有数排序。维护一个大小为 k 的小顶堆:

  • 新元素直接入堆;
  • 堆超过 k 个时,弹出堆顶(当前 k 个里最小的);
  • 扫描结束后,堆里正好是最大的 k 个,堆顶就是第 k 大。
分步图解

维护一个大小为 4 的小顶堆:新元素入堆,超过 k 个就弹掉堆顶。

小顶堆 · 只保留最大的 4 个左子树 · 右子树

空树 · null

输入 · 灰色已处理
0
1
2
3
4
5
6
7
8
7
2
9
4
8
1
5
9
3
答案候选
[ ]
1 / 4
前 k 个元素先填满堆;堆顶始终是这 k 个中最小的。

用小顶堆而不是大顶堆,是因为我们要淘汰“最大的 k 个里最小的那个”。若用大顶堆,堆顶是全局最大,反而无法判断谁该出局。

分步图解

8 入堆,堆超过 4 个,弹掉最小的 2;当前堆顶 4。

heap.size
4
小顶堆 · 只保留最大的 4 个左子树 · 右子树
4堆顶798
输入 · 灰色已处理
0
1
2
3
4
5
6
7
8
7
2
9
4
8
1
5
9
3
i
答案候选
堆顶 4
1 / 4
新元素比堆顶大时替换堆顶;比堆顶小的元素直接淘汰。

自拟数组有两个 9,k=4:堆最终保留 [7,8,9,9],堆顶 7 就是第 4 大。重复值不会影响堆的大小逻辑。

时间 O(n log k),空间 O(k)。k 远小于 n 时比排序更省。

时间 O(n log k)空间 O(k)

易错点

  • 用大顶堆:堆顶是最大值,无法在超过 k 个时淘汰最小者。
  • 先全部入堆再弹 n-k 次:复杂度接近 O(n log n),没有利用 k。
  • 把“第 k 大”理解成第 k 个不同值:重复元素要分别计数。
对照解法

快速选择 · 平均 O(n)

像快排一样分区,只递归包含答案的那一侧

平均 O(n),但最坏 O(n²),且会修改数组;面试中要主动说明随机化或最坏情况。

快排的分区会把数组分成“小于等于 pivot”和“大于 pivot”两部分。设右段(含 pivot)有 count 个元素:

  • count == k:pivot 就是第 k 大;
  • count > k:答案在右段,只递归右边;
  • count < k:答案在左段,变成第 k-count 大。

每次只递归一侧,平均时间 O(n)。但如果每次 pivot 都选到端点,会退化成 O(n²);工程上可以随机选 pivot 或打乱数组。

它比堆更快,但会修改输入,且最坏情况不稳定。

  1. 1以区间末尾为 pivot 分区。
  2. 2统计右段元素个数 count。
  3. 3count 与 k 比较,只递归一侧。
  4. 4命中时返回 pivot。
时间 平均 O(n),最坏 O(n²)空间 O(log n) 递归栈

易错点

  • 两边都递归:退化成快速排序,O(n log n)。
  • count 的计算漏掉 pivot 本身:差 1。
  • 不随机化且输入有序:最坏情况容易触发。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入nums = [7,2,9,4,8,1,5,9,3],k = 4

维护一个大小为 4 的小顶堆:新元素入堆,超过 k 个就弹掉堆顶。

小顶堆 · 只保留最大的 4 个左子树 · 右子树

空树 · null

输入 · 灰色已处理
0
1
2
3
4
5
6
7
8
7
2
9
4
8
1
5
9
3
答案候选
[ ]
1 / 4
查看 C++ 代码

延伸阅读

  • “第 k 大”与“最大的 k 个”是两种不同输出:前者只要一个值,后者要一个集合。
  • 堆适合流式数据;快速选择适合一次性、可修改的数组。