215-数组中第k个最大元素
返回给定整数数组
nums和整数k,请返回数组中第 k 个最大的元素。
注意:你需要找的是数组排序后的第k个最大的元素,而不是第k个不同的元素。
要求:设计并实现时间复杂度为 (O(n)) 的算法。
思路:Quickselect(快速选择)
核心就是“快速排序的 partition”,但只递归/迭代一侧。
- 把“第 k 大”转换成“升序排序后下标为
len - k的元素”。 - 每次
partition把一个pivot放到它的最终位置j:<= pivot在左边,>= pivot在右边。
- 如果
j == targetIndex,直接返回;否则只去j的左半或右半继续找。
期望时间复杂度 (O(n)),空间复杂度 (O(1))(原地交换)。
代码实现(Java)
import java.util.Random;
class Solution {
// 随机数生成器:用于随机选择 pivot,避免在某些特殊数据上退化到 O(n^2)
private static final Random rand = new Random();
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
// 把“第 k 大”转换为“升序排序后下标为 len-k 的元素”
int targetIndex = len - k;
// 当前处理的子数组范围 [left, right]
int left = 0;
int right = len - 1;
// 不断在子数组中做划分,直到 pivot 的最终位置正好等于 targetIndex
while (true) {
int i = partion(nums, left, right);
if (i == targetIndex) {
return nums[i];
} else if (i > targetIndex) {
right = i - 1;
} else {
left = i + 1;
}
}
}
/**
* 在子数组 [left, right] 上做一次划分(Hoare 风格):
* 返回 pivot 的最终下标 j
*/
private int partion(int[] nums, int left, int right) {
// 1) 随机选 pivot 下标,并把 pivot 交换到最左边
int i = left + rand.nextInt(right - left + 1);
int pivot = nums[i];
swap(nums, i, left);
// 2) 双指针扫描 [left+1, right]
i = left + 1;
int j = right;
while (true) {
while (i <= j && nums[i] < pivot) i++;
while (i <= j && nums[j] > pivot) j--;
if (i >= j) break;
swap(nums, i, j);
i++;
j--;
}
// 3) pivot 放回最终位置 j
swap(nums, j, left);
return j;
}
private void swap(int[] nums, int a, int b) {
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
两个常见问题
1)为什么要随机选 pivot?
- 防止退化:如果总选固定位置(如
left),遇到接近有序等数据时,partition 可能每次只缩小 1 个元素,时间变成 (O(n^2))。 - 随机更稳:随机 pivot 让“切分比较均衡”的概率更高,整体期望时间是 (O(n))。
2)为什么把 pivot 先换到最左边?
- 省心:把 pivot 固定在
left,双指针只扫描[left+1, right],不会“误操作”到 pivot。 - 收尾简单:分区结束后
j就是 pivot 该去的位置,直接swap(left, j)即可。