本题主要是返回数组排序之后的倒数第k个位置
方法一:基于快速排序
思路和算法
我们可以用快速排序来解决这个问题,先对原数组排序,再返回倒数第 k 个位置,这样平均时间复杂度是 O(nlogn),但其实我们可以做的更快
首先我们来回顾一下快速排序,这是一个典型的分治算法。我们对数组 a[l⋯r]做快速排序:
由此可以发现每次经过「划分」操作后,我们一定可以确定一个元素的最终位置,即 x的最终位置为 q,并且保证 a[l⋯q−1] 中的每个元素小于等于 a[q],且 a[q] 小于等于 a[q+1⋯r]中的每个元素。所以只要某次划分的 q 为倒数第 k 个下标的时候,我们就已经找到了答案。 我们只关心这一点,至于 a[l⋯q−1]和 a[q+1⋯r]是否是有序的,我们不关心
因此我们可以改进快速排序算法来解决这个问题:在分解的过程当中,我们会对子数组进行划分,如果划分得到的 q 正好就是我们需要的下标,就直接返回 a[q];否则,如果 q比目标下标小,就递归右子区间,否则递归左子区间。这样就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。这就是「快速选择」算法。
class Solution {
int quickselect(int[] nums, int l,int r,int k) {
if(l == r ){
return nums[k];
}
int x = nums[l],i = l - 1,j = r + 1;
while(i < j){ //下面过程就是进行快速排序
do {
i++;
}while(nums[i] < x); //找到左区间大于x的位置
do{
j--;
}while(nums[j] > x); //找到右区间小于x的位置
if(i<j){ //将两者进行交换
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
if(k<=j){ //x比目标下标大,递归左子区间
return quickselect(nums,l,j,k);
}else{ //x比目标下表小,递归右子区间
return quickselect(nums,j+1,r,k);
}
}
public int findKthLargest(int[] _nums,int k){
int n = _nums.length;
//第K个最大及第n-k个大的(从0开始)
return quickselect(_nums, 0, n-1, n-k);
}
}
方法二:基于堆排序
我们也可以使用堆排序来解决这个问题——建立一个大根堆,做 k−1k - 1k−1 次删除操作后堆顶元素就是我们要找的答案。在很多语言中,都有优先队列或者堆的的容器可以直接使用
class Solution {
public int findKthLargest(int[] nums,int k){
int headSize = nums.length;
buildMaxHeap(nums,headSize);
for(int i = nums.length - 1; i>= nums.length-k+1; --i){
swap(nums,0,i);
--headSize;
maxHeapify(nums,0,headSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a,int headSize){
for(int i = headSize/2; i>=0;--i){
maxHeapify(a,i,headSize);
}
}
public void maxHeapify(int[] a,int i ,int headSize){
int l = i * 2 + 1,r = i * 2 + 2,largest = i;
if(l<headSize && a[l] > a[largest]){
largest = l;
}
if(r < headSize && a[r] > a[largest]){
largest = r;
}
if(largest != i){
swap(a,i,largest);
maxHeapify(a,largest,headSize);
}
}
public void swap(int[] a,int i ,int j){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}