给定整数数组 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
1. 快速选择算法 (Quick Select) —— “寻找接班人的选拔赛”
这是图一中的核心解法,也是面试官最想看到的 平均时间复杂度的算法。
-
生活案例:选拔全校身高第 K 名的学生
-
划分 (Partition) :你随机挑一个学生(Pivot),让比他高的站左边,比他矮的站右边。
-
判断:
- 如果“高个子组”的人数正好是 个,那么这个 Pivot 就是第 名,直接带走。
- 如果“高个子组”人太多了,说明第 名还在左边,你只需要去左边继续这个过程,右边的人可以回教室了。
- 反之,去右边找。
-
-
代码逻辑:
JavaScript
const quickSelect = (left, right) => { if (left >= right) return; // 随机挑一个基准值,避免最坏情况 let pivot = nums[Math.floor(Math.random() * (right - left + 1)) + left]; let lt = left, gt = right, i = left; // 三路划分:小于、等于、大于基准值 while (i <= gt) { if (nums[i] < pivot) [nums[i], nums[lt]] = [nums[lt], nums[i]], i++, lt++; else if (nums[i] > pivot) [nums[i], nums[gt]] = [nums[gt], nums[i]], gt--; else i++; } // 只去目标索引 target 所在的区间递归 if (target >= lt && target <= gt) return; else if (target < lt) quickSelect(left, lt - 1); else quickSelect(gt + 1, right); };
2. 桶排序 (Bucket Sort) —— “超市理货员的分类筐”
这是图二中的解法。它极快,但前提是数据范围不能太离谱。
-
生活案例:整理 100 分制下的试卷
- 你面前有 101 个筐(从 0 分到 100 分)。
- 你把试卷往对应的筐里一扔。
- 找第 名时,你从 100 分的筐开始往回数,数到第 张试卷,那个筐的标签就是分数。
-
代码逻辑:
JavaScript
var findKthLargest = function(nums, k) { let offset = 10000; // 处理负数 let bucket = new Array(20001).fill(0); // 准备 20001 个“筐” for (let num of nums) { bucket[num + offset]++; // 往对应分数的筐里加 1 } let count = 0; // 从大分数的筐开始往回找 for (let i = bucket.length - 1; i >= 0; i--) { count += bucket[i]; if (count >= k) return i - offset; // 数够了 K 个,收工 } };
3. 直接排序 (Library Sort) —— “交给专业物流公司”
这是图三中的解法。这是最直观、代码量最少的保底方案。
-
生活案例:快递公司自动化分拣
- 你懒得分类,也懒得选拔,直接把所有包裹推上全自动分拣流水线。
- 机器会帮你把所有东西排好序,你直接去传送带的第 个位置拿就行了。
- 代价:这需要消耗更多的能源(时间复杂度 )。
-
代码逻辑:
JavaScript
var findKthLargest = function(nums, k) { // 直接调用系统内置排序,降序排列 let arr = nums.sort((a, b) => b - a); return arr[k - 1]; // 取第 K 个 };
总结对比
| 方法 | 生活比喻 | 优点 | 缺点 |
|---|---|---|---|
| 快速选择 | 精准选拔赛 | 平均 ,面试首选 | 代码实现相对复杂 |
| 桶排序 | 分筐理货 | 稳定 ,数据集中时极快 | 数字范围太大时浪费内存(空间换时间) |
| 直接排序 | 自动化分拣 | 代码极简,易于理解 | 时间复杂度 ,效率不如前两者 |