题目:
在未排序的数组中找到第 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 ≤ 数组的长度。
一般求解第K大或第K小的问题都可以通过快排和优先队列(最小堆、最大堆)来解决。
方法1:使用快速排序进行分治
众所周知,每次快排都可以固定某个元素的位置,假设这个元素为pivot,下标为j,那么快排结束后在它左边的都是小于pivot的,在它右边都是大于pivot的。就有:
-
区间[left, j-1]小于pivot
-
区间[j+1,right]大于pivot
j就是第x大的元素下标
第K大元素下标为len-k(len为数组长度)
-
如果j=len-K,表示j就是第K大的元素
-
如果j>len-K,就表示当前元素找大了,表示第K大的元素在左区间[left, j-1],就去左部分区间快排查找
-
如果j<len-K,就表示当前元素找小了,表示第K大的元素在区间[j+1,right],就去右部分区间快排查找
代码:
public class Solution {
private static Random random = new Random(System.currentTimeMillis());
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
// 第k大元素的下标是len - k
int target = len - k;
while (true) {
//得到某元素的排好序固定位置的下标
int index = partition(nums, left, right);
if (index == target) {
//说明找到了
return nums[index];
} else if (index < target) {
//找小了,去右部分区间找
left = index + 1;
} else {
//找大了,去左部分区间找
right = index - 1;
}
}
}
public int partition(int[] nums, int left, int right) {
// 在区间随机选择一个元素作为标定点
if (right > left) {
int randomIndex = left + 1 + random.nextInt(right - left);
swap(nums, left, randomIndex);
}
int pivot = nums[left];
// 将大于和小于pivot的元素分散到两边
int lt = left + 1;
int rt = right;
while (true) {
//右移,直到找到一个不小于pivot的,作为交换元素
while (lt <= rt && nums[lt] < pivot) {
lt++;
}
//左移,直到找到一个不大于pivot的,作为交换元素
while (lt <= rt && nums[rt] > pivot) {
rt--;
}
//结束查询,此时rt下标指向了左部分区间的最右端,lt指向了右部分区间的最左端
if (lt > rt) {
break;
}
//进行交换,指针移动并继续查询
swap(nums, lt, rt);
lt++;
rt--;
}
//固定pivot位置,此时[left,rt]区间都是小于pivot的
//交换了以后,就有了[left,rt-1]、[rt]、[lt,right]三部分
//rt就是这次快排后,pivot应该在的位置下标,返回
swap(nums, left, rt);
return rt;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
复杂度分析:
时间复杂度:O(n)。因为每次只操作一半的元素。所以就是N+N/2+N/4+…+1,和为O(n)
空间复杂度:O(1)
方法2:最小堆
思路比较简单,维护一个有K个元素的最小堆。
遍历数组,如果堆不满,直接添加;如果满了,则将遍历到的数x和堆顶元素比较(堆中最小元素),如果大于,弹出栈顶元素,并将x加入,否则不需要操作
代码:
public class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
//默认最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
//先加入K个元素
for (int i = 0; i < k; i++) {
minHeap.add(nums[i]);
}
//遍历剩余元素
for (int i = k; i < len; i++) {
// 如果当前元素比栈顶元素大,弹出栈顶元素并加入num[i]
if (nums[i] > minHeap.peek()) {
minHeap.poll();
minHeap.add(nums[i]);
}
}
return minHeap.peek();
}
}