题目描述
返回数组中的第K个最大的元素。
解题思路
快速排序:主要思想就是通过划分将待排序的序列分成前后两部分,前一部分数据都比后一部分数据小,然后再递归调用函数对两部分序列进行快速排序,以便使整个序列达到有序。
划分函数需要一个分界值,然后再进行划分,可以采用随机的方式,即对于区间[l,r]中的数等概率随机一个作为主元,再将主元放到区间末尾,进行划分。
整个划分过程主要涉及两个指针i
和j
,一开始i=l-1,j=l
,需要实时维护两个指针,使得任意的时候,对于任意数组下标k
都有以下条件成立:
l≤k≤i 时,nums[k]≤pivot
i+1<=k<=j-1时,nums[k]>pivot
k==r 时,nums[k]=pivot
每次移动指针j
,如果nums[j]>pivot
,我们只需要继续移动指针j
就可以使得上述三个条件成立,否则我们需要将指针i
加1,然后交换nums[i
和nums[j]
,再移动j
就可以使得三个条件成立。
当j
移动到r-1
时循环结束,此时[l,r]
中的数都是小于等于主元pivot
的,[i+1,r-1]
之间的元素都是大于主元pivot
,只需要交换nums[i+1]
和nums[r]
就可以使得[l,i+1]
区间的数都小于[i+2,r]
区间的数,完成一次划分,且分界值下标为i+1
,返回即可。
动图展示:
https://leetcode-cn.com/problems/sort-an-array/solution/pai-xu-shu-zu-by-leetcode-solution/
整体流程:
4. 随机确定一个主元
5. 将主元放到数组的最右侧
6. 然后令i=l-1,j=l来找比主元小的元素就与i+1位置的元素交换,最后再将主元与i+1位置的元素做交换,最后使得[l,i]都是小于主元的元素,[i+1,r]都是大于等于主元的元素。(每次i+1位置的元素的值都是大于主元的)
代码实现
lass Solution {
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
// 要找到的元素所在索引: 前K大,即倒数索引第K个
int index = nums.length - k;
int right = nums.length - 1;
int left = 0;
return quickSelect(nums, left, right, index);
}
public int quickSelect(int[] nums, int left, int right, int index) {
// 得到分区值索引q
int q = randomPartition(nums, left, right);
if (q == index) {
// 如果刚好索引q就是想要的索引,则直接返回
return nums[q];
} else {
// 如果不是,比较q 与 index ,确定下次要检索的区间, 要么是[q+1, right], 要么就是[left, q-1]
return q < index ? quickSelect(nums, q + 1, right, index) : quickSelect(nums, left, q - 1, index);
}
}
public int randomPartition(int[] nums, int l, int r) {
// 1. 随机数范围: [0, r-l+1) 同时加l, 则是 [l, r+1) = [l, r] 也就是在这个[l,r] 中随机选一个索引出来
int i = random.nextInt(r - l + 1) + l;
// 2. 交换nums[i], nums[r], 也就是将随机数先放在[l,r]最右边nums[r]上
swap(nums, i, r);
return partition(nums, l, r);
}
public int partition(int[] nums, int l, int r) {
// 3. 在调用当前方法的randomPartition方法中,已经确定了了随机数是nums[r]
int x = nums[r], i = l - 1;
// 首先比较区间在[l, r]之间, 所以nums[j]中的 l<= j <= r
for (int j = l; j < r; ++j) {
// 4. nums[j] 跟随机数 x 比较, 小于x的数都跟[l,r]左边区间交换,i=l-1,所以++i=l,初始索引就是l,
if (nums[j] <= x) {
swap(nums, ++i, j);//两两交换
}
}// 这个for循环操作就是将小于 x 的数都往[i, j]的左边区间设置,从而实现存在[l, i]区间,使得对应数值都 小于 x
//5. 既然已经将<x的值都放在一边了,现在将x也就是nums[r] 跟nums[i+1]交换,从而分成两个区间[l.i+1]左, [i+2, r]右,左边区间的值都小于x
swap(nums, i + 1, r);
// 然后返回这个分区值
return i + 1;
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}