跳到主要内容

BM47. 寻找第K大

题目描述​

有一个整数数组 a,请你根据快速排序的思路,找出数组中第 K 大的数。 给定数组 a、大小 n 和要找的 K,请返回第 K 大的数(包含重复的元素,不去重), 保证答案存在。

输入格式​

  • a:int 型一维数组;
  • n:int 型,数组长度;
  • K:int 型,要找第 K 大。

输出格式​

返回 int 型,第 K 大的数(含重复元素)。

示例​

示例 1​

输入:

a = [1,3,5,2,2], n = 5, K = 3

输出:

2

说明: 升序为 [1,2,2,3,5],第 3 大(从大到数)是 2。

示例 2​

输入:

a = [10,10,9,9,8,7,5,6,4,3,4,2], n = 12, K = 3

输出:

9

说明: 去重后的第 3 大是 8,但本题要求包含重复元素,不去重,所以输出 9。


解题思路​

第一步:理解问题本质​

「第 K 大(含重复)」= 把数组升序排序后,第 n - K 个元素(下标从 0 起)。 例如 n=5、K=3,就是排序后的下标 2。

关键是不去重:不能先 set 再取,否则结果会偏大。

第二步:直接排序​

return sorted(a)[n - K]

O(n log n),思路最直白,但题目提示要「根据快速排序的思路」, 用快速选择更快。

第三步:快速选择(Quickselect)​

快速选择是「只做一半的快排」:

  1. 选一个基准 pivot,做一次三路划分,把数组分成三段: 小于 pivot、等于 pivot、大于 pivot;
  2. 目标下标 target = n - K:
    • 若落在「小于」段,只递归处理左半;
    • 若落在「大于」段,只处理右半;
    • 若落在「等于」段,说明 pivot 就是答案,直接返回。

每次只处理一侧,规模期望减半,平均 O(n)。 用三路划分(而不是普通两路)是因为数组含大量重复 —— 两路划分在「全相等」时会退化成 O(n²),三路则一次就把等于段整体定位。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码99 帧 · 录制输入 [10,10,9,9,8,7,5,6,4,3,4,2], 12, 3
from typing import List


class Solution:
def findKth(self, a: List[int], n: int, K: int) -> int:
"""原地快速选择,返回升序第 n-K 个元素(即第 K 大)"""
target = n - K # 第 K 大 == 升序里下标 n-K 的元素
lo, hi = 0, n - 1
while True:
pivot = a[(lo + hi) // 2]
lt, i, gt = lo, lo, hi # 三路划分:<pivot 放左边,>pivot 放右边
while i <= gt:
if a[i] < pivot:
a[lt], a[i] = a[i], a[lt]
lt += 1
i += 1
elif a[i] > pivot:
a[gt], a[i] = a[i], a[gt]
gt -= 1
else:
i += 1
if target < lt:
hi = lt - 1
elif target > gt:
lo = gt + 1
else:
return pivot

示例推演​

以 a = [1,3,5,2,2], n = 5, K = 3 为例,target = 2:

| 轮次 | 区间 | pivot | 划分后(< | = | >) | target=2 落在 | |------|------|-------|---------------------|----------------| | 1 | [0,4] | 5 | [1,3,2,2] , [5] | 落在「< 5」段(下标 0..3) | | 2 | [0,3] | 3 | [1,2,2] , [3] | 落在「< 3」段(下标 0..2) | | 3 | [0,2] | 2 | [1] , [2,2] , [] | target=2 落在「= 2」段 → 返回 2 |

结果 2,与答案一致。


复杂度分析​

解法时间复杂度空间复杂度说明
排序O(n log n)O(n)最简单
快速选择平均 O(n)O(1)推荐,原地
快速选择(最坏)O(n²)O(1)基准选得极差时

易错点总结​

1. 下标换算搞反​

「第 K 大」对应升序下标 n - K,不是 K - 1。写错会取到第 K 小。

2. 去了重​

题目要求包含重复元素,两个相同的值各占一个名次,别用 set。

3. 用两路划分​

普通两路划分遇到大量重复(如全相等)会退化。用三路划分更稳。


扩展思考​

  • 「第 K 小」只差一个下标换算:升序下标 K - 1。
  • 若要「第 K 大的 k 个数」,直接用小顶堆维护 k 个(BM46)。

相关题目​

自测​

加载评论中...