一、题目
给定整数数组 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
二、代码
class Solution {
public static int findKthLargest(int[] nums, int k) {
// 数组长度
int n = nums.length;
// 当前这一轮改进后快排的左区间
int L = 0;
// 当前这一轮改进后快排的右区间
int R = n - 1;
// 当前这一轮改进后快排的左部分的边界,即L~l范围上都是小于v的数,l指向的也是小于v的数
int l = L - 1;
// 当前这一轮改进后快排的右部分的边界,即r~R范围上都是大于v的数,r指向的也是大于v的数
int r = R + 1;
// 找第k大的数,也就相当于找第n-k+1小的数
// 但是数组下表是从0开始的,所以这里就是n-k即可
k = n - k;
while (true) {
// 从L~R范围上随机找一个数作为基准
// 这里先利用随机函数L + Math.random() * (R - L + 1)随机生成下标L~R范围上的数
// 注意这里要注意在前面加上L,不然求出来的就是随机0~R-L了,我们需要随机L~R
int v = nums[L + (int) (Math.random() * (R - L + 1))];
// 下面的流程就是荷兰国旗问题了,将数组中小于v的放左边,等于v的放中间,大于v的放右边
// 然后用l和r来标记三个部分的边界
// 当前遍历到的位置,从L开始
int cur = L;
// 开始讲数组进行移动
while (l < r && cur < r) {
// 小于v的放左边
if (nums[cur] < v) {
// cur放到++l位置
swap(++l, cur, nums);
// 大于v的放右边
} else if (nums[cur] > v) {
// cur放到--r位置
swap(--r, cur, nums);
// 因为交换到cur位置的数是在右侧,还没有判断过他,所以交换到cur后还是需要在判断一次cur这个新数的大小,所以这里不执行cur++
continue;
}
// 继续向右遍历判断
cur++;
}
// 至此,小于v的都在小于等于l的位置 l位置是小于v的数
// 等于v的范围就是大于l小于r
// 大于v的都在大于等于r的位置 r位置是大于v的数
if (k > l && k < r) {
// 如果k落在等于v范围上,直接返回v,v就第k小的数
return v;
// 如果此时k落在左部分,那么我们再去左部分去进行荷兰国旗操作
} else if (k <= l) {
// 新的范围就是L~l
// 更新R
R = l;
// 在新的范围上初始化r和l
r = l + 1;
l = L - 1;
// 如果此时k落在右部分,那么我们再去右部分去进行荷兰国旗操作
} else {
// 新的范围就是r~R
// 更新L
L = r;
// 在新的范围上初始化r和l
l = L - 1;
r = R + 1;
}
}
}
// 交换操作
public static void swap(int i, int j, int[] nums) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
三、解题思路
改进的快排算法,只选一侧, 复杂度O(N)。
无序数组中随机选一个数,这个数叫V,拿它去做整个数组的荷兰国旗问题。
对于V来来说,<它放左边,等于它放中间,>它的放右边。
如果等于的范围能命中K这个数就停,也就是说K这个数在等于的范围内部。如果没命中,然后根据数据状况对左侧或者右侧再随机选。
一个数做荷兰国旗问题,递归下去,总能有找到第K小数的时候。
如果没命中,要么走左边,要么走右边,它是只走一侧的,所以比快排要快,快排需要两侧都要走。