[215. 数组中的第K个最大元素]
难度 中等
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array
解法一:基于快速排序的选择
可以采用快速排序将数组中的元素降序排列,并返回下标为 k - 1的元素,快速排序的时间复杂度为O(n*logn)。
不过我们可以根据快速排序的特性对其进行优化,快速排序中每一次排序后基准数都会归为到正确的位置,因此我们可以将基准数归为后的下标 q 与 k - 1 进行比较,
- 如果 q < k - 1则在基准数的右边寻找 quickSelectSort(a, q + 1, r, index)
- 如果 q > k - 1则在基准数的左边寻找 quickSelectSort(a, l, q - 1, index)
- 如果 q == k - 1则返回 nums[q]
class Solution {
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelectSort(nums, 0, nums.length - 1, k - 1);
}
private int quickSelectSort(int[] a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if(q == index) {
return a[q];
}else {
return q < index ? quickSelectSort(a, q + 1, r, index) : quickSelectSort(a, l, q - 1, index);
}
}
//产生[l, r] 范围内的随机数作为基准数的位置
private int randomPartition(int[] a, int l, int r) {
int i = random.nextInt(r - l + 1) + l;
//交换 i 和 r 的位置使得基准数都位于末端,方便统一操作
swap(a, i, r);
//以该基准数作为标准进行一次排序,返回基准数排序后的正确位置
return partition(a, l, r);
}
private int partition(int[] a, int l, int r) {
int x = a[r], i = l - 1;
for(int j = l; j < r; j++) {
if(a[j] >= x) {
swap(a, ++i, j);
}
}
swap(a, i + 1, r);
return i + 1;
}
private void swap(int[] a, int i, int j) {
if(i != j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
复杂度分析
- 时间复杂度:O(n),证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」。
空间复杂度:O(logn),递归使用栈空间的空间代价的期望为 O(logn)。
解法二:基于堆排序的选择
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
//建立初始大根堆
MaxHeapSort(nums, heapSize);
//调整得到第 K 个最大元素, 需要调整 k - 1 次
for (int i = 1; i < k; i++) {
swap(nums, 0, heapSize - 1);
heapSize--;
sift(nums, 0, heapSize);
}
//返回第 K 个最大元素
return nums[0];
}
//建立初始堆
public void MaxHeapSort(int[] a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
sift(a, i, heapSize);
}
}
public void sift(int[] a, int low, int heapSize) {
int i = low, j = 2 * i; //a[j] 是 a[i] 的左孩子
int temp = a[i];
while(j < heapSize) {
if(j < heapSize - 1 && a[j] < a[j + 1]) {
j++; //指向孩子节点中的较大节点
}
if(temp < a[j]) {
a[i] = a[j];
i = j;
j = 2 * i;
}else {
break; //双亲大,则不在调整
}
}
a[i] = temp;
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}