图解题库 / 快速选择 · Top K

215. 数组中第 K 个最大元素

中等 含交互动画

学完这道你应该能: 说清它为什么用「Top K」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

给一个数组,找出其中第 k 个最大的元素(是排序后的第 k 大,不是第 k 个不同的)。

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

思路解析

排一遍当然行,O(n log n)。但题目只要「第 K 大」一个数,把整个数组都排好太亏了。

快排里 partition 一轮,能让基准归到它最终的位置,左边都比它小、右边都比它大。我们只关心「第 K 大该在的那个下标」,所以只递归含目标的那一半,另一半直接扔——这就是快速选择。

目标:找排序后下标 4 的数:六根柱子。第 2 大=排序后下标 4 的那个。我们不排序,用分区一步步把它逼出来。

第 1 轮分区:选末尾 4 当基准:选最后一个 4 当基准(橙色)。把比 4 小的甩到左边、比 4 大的留右边。

4 归位到下标 3:分完区,4 落到了下标 3(绿色=已归位)。3、2、1 在它左边,6、5 在它右边。

4 在下标 3 < 目标 4 → 只看右半:基准在下标 3,我们要的是下标 4,在它右边——所以只递归右半 [6,5],左边那一整段(灰)看都不用看。这就是它比快排省的地方。

第 2 轮分区:右半选 5 当基准:只在右半 [6,5] 里继续。选末尾 5 当基准。

5 归位到下标 4:6 > 5,5 归到了下标 4。

下标 4 == 目标 → 答案 = 5:基准 5 正好落在下标 4(= n−k),它就是第 2 大。返回 5,结束——全程没把整个数组排完。

快排两边都递归(要全排好);快速选择只要找第 K 个,只走含目标的那一边,所以更快。「第 K 大/小」「找中位数」都用它。

参考代码(Python)

Python
1import random
2def findKthLargest(nums, k):
3    target = len(nums) - k             # 第K大 = 排序后下标 target
4    lo, hi = 0, len(nums) - 1
5    while True:
6        p = partition(nums, lo, hi)    # 基准归位后的下标
7        if p == target: return nums[p]
8        elif p < target: lo = p + 1    # 只往含目标的一边走
9        else: hi = p - 1

复杂度分析

  • 平均:O(n) —— 每轮规模减半,n+n/2+…≈2n
  • 最坏:O(n²) —— 基准选偏(随机化避免)

套路模板

骨架:target = n−k、partition 拿到归位下标 p、只往 p 偏向 target 的那边收。和快排唯一的差别就是「只递归一边」。

Python
1def quick_select(nums, k):
2    target = len(nums) - k        # 第K大的下标
3    lo, hi = 0, len(nums) - 1
4    while lo <= hi:
5        p = partition(nums, lo, hi)
6        if p == target: return nums[p]
7        if p < target: lo = p + 1     # 只走一边
8        else: hi = p - 1

易错点

  • 第 K 大用下标 k → ✅ 下标是 n − k(第 K 大=排序后倒数第 k 个,下标要从右数)
  • partition 后两边都递归 → ✅ 只递归含目标的一边(两边都递归就退化成快排 O(n log n),丢了快速选择的优势)
  • 固定拿末尾当基准 → ✅ 随机选基准再换到末尾(碰上已排好的数组,固定选末尾会退化成 O(n²);随机一下就稳了)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →347. 前 K 个高频元素 ← 返回题库