文章目录
-
- 一、题目说明
- 二、思维导图
- 三、解法一:排序法(暴力思路)
- 四、解法二:堆排序思路(小顶堆法)
- [五、解法三:快速选择法(Quick Select)](#五、解法三:快速选择法(Quick Select))
- 六、不同解法对比表
一、题目说明
题目链接 :LeetCode - 数组中的第 K 个最大元素
题目描述:
给定一个整数数组 nums 和一个整数 k,请返回数组中第 k 个最大的元素。
注意: 这是指排序后的第 k 个最大元素,而不是第 k 个不同的元素。
示例:
输入:nums = [3,2,1,5,6,4], k = 2
输出:5
输入:nums = [3,2,3,1,2,4,5,5,6], k = 4
输出:4
二、思维导图
下面是本题常见四种解法的思考路线:
第 K 个最大元素
排序法
时间复杂度 O(n log n)
直接排序取第 k 个
堆法(优先队列)
小顶堆保存前 k 大元素
时间复杂度 O(n log k)
空间复杂度 O(k)
快速选择法 (Quick Select)
基于快速排序思想
平均复杂度 O(n)
最坏复杂度 O(n^2)
冒泡法思路 (优化较差)
重复扫描取最大值
时间复杂度 O(k * n)
不推荐
三、解法一:排序法(暴力思路)
原理
最直接的方式------先排序,再取第 k 个最大值。
将数组从大到小排序后,取下标为 k-1 的元素即可。
流程图
输入数组 nums 和 k
对 nums 进行降序排序
返回 nums[k-1]
代码实现(Java)
java
public class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
}
复杂度分析
- 时间复杂度: O(n log n)
排序需要 O(n log n) - 空间复杂度: O(1)
若使用原地排序,则不额外占用空间。
优缺点:
- ✅ 简单易实现
- ❌ 对大规模数据性能较差
四、解法二:堆排序思路(小顶堆法)
原理
可以用 最小堆(Min Heap) 来维护当前前 k 大的元素:
- 遍历数组,将元素加入堆中;
- 每当堆大小超过 k,就弹出最小值;
- 遍历完毕后,堆顶即是第 k 个最大元素。
思维示意
是
否
初始:堆为空
逐步插入元素
堆大小 > k?
弹出堆顶最小
继续插入
遍历完成 -> 堆顶即为第K大
代码实现(Java)
java
import java.util.PriorityQueue;
public class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>(); // 小顶堆
for (int n : nums) {
heap.offer(n);
if (heap.size() > k) {
heap.poll(); // 弹出堆顶最小元素
}
}
return heap.peek(); // 堆顶就是第k大元素
}
}
复杂度分析
- 时间复杂度: O(n log k)
每次插入和可能的弹出操作都是 O(log k) - 空间复杂度: O(k)
优缺点:
- ✅ 性能好,适合大数据
- ✅ 可用于流式数据场景(可维护前 k 大元素)
- ❌ 实现稍复杂
五、解法三:快速选择法(Quick Select)
原理
该方法基于快速排序的"分区(Partition)"思想:
- 选定一个基准(pivot),将数组分为两部分:
- 左侧大于 pivot 的元素
- 右侧小于 pivot 的元素
- 根据 pivot 的位置,判断是否正好是第 k 大:
- 如果
pivotIndex == k,直接返回; - 否则在相应区间递归查找。
- 如果
时序图
pivot选取 分区操作 当前数组 pivot选取 分区操作 当前数组 执行 partition(nums) 选取 pivot,调整左右区域 返回 pivotIndex 判断 pivotIndex 与 k 的关系
代码实现(Java)
java
import java.util.Random;
public class Solution {
public int findKthLargest(int[] nums, int k) {
int target = nums.length - k;
return quickSelect(nums, 0, nums.length - 1, target);
}
private int quickSelect(int[] nums, int left, int right, int k) {
if (left == right) return nums[left];
int pivotIndex = partition(nums, left, right);
if (pivotIndex == k) {
return nums[pivotIndex];
} else if (pivotIndex < k) {
return quickSelect(nums, pivotIndex + 1, right, k);
} else {
return quickSelect(nums, left, pivotIndex - 1, k);
}
}
private int partition(int[] nums, int left, int right) {
int pivot = nums[right];
int i = left;
for (int j = left; j < right; j++) {
if (nums[j] <= pivot) {
swap(nums, i, j);
i++;
}
}
swap(nums, i, right);
return i;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
复杂度分析
- 平均时间复杂度: O(n)
- 最坏情况: O(n²)
- 空间复杂度: O(1)
优缺点:
- ✅ 平均性能优秀
- ✅ 不需要额外空间
- ❌ 最坏情况下可能退化为 O(n²)
六、不同解法对比表
| 解法类型 | 平均时间复杂度 | 空间复杂度 | 适合场景 | 备注 |
|---|---|---|---|---|
| 排序法 | O(n log n) | O(1) | 小数据量 | 简单易实现 |
| 堆法 | O(n log k) | O(k) | 大数据或流式数据 | 常用方案 |
| 快速选择法 | O(n) | O(1) | 通用高性能场景 | 平均最优 |
- 若是面试题,推荐使用 堆法 或 快速选择法;
- 排序法虽简单,但效率一般;
- 快速选择法在平均情况下非常快,是最优选择之一;
- 小顶堆适合处理实时的前 k 大问题,例如实时数据流。