目录
https://leetcode.com/problems/kth-largest-element-in-an-array/
返回数组中第k大的元素
一、问题分析
Input: [3,2,3,1,2,4,5,5,6] and k = 4
Output: 4
输出的是第k大的元素,注意不是第k大的不同元素。另外,k总是大于等于1。
二、代码实现
1、排序
采用内置排序
采用Arrays.sort(),然后返回nums[nums.length-k]即可,时间为O(nlogn)。
import java.util.Arrays;
import java.util.Collections;
class Solution {
public int findKthLargest1(int[] nums, int k) {
//排序
Arrays.sort(nums);
return nums[nums.length-k];
//Arrays.sort(nums, Collections.reverseOrder());
//return nums[k - 1];
}
}
采用冒泡排序
采用用冒泡的方式,进行k轮冒泡。
class Solution {
public int findKthLargest1(int[] nums, int k) {
//冒泡
for (int i=0; i<k; i++) { //控制冒泡的轮数为k
for (int j=0; j<nums.length-1; j++) {
if (nums[j] > nums[j+1]) {
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
return nums[nums.length-k];
}
}
基数排序
class Solution {
//LSD radix sort
public int findKthLargest9(int[] nums, int k) {
final int R = (1 << 8);
final int bitmask = R - 1;
int[] aux = new int[nums.length];
for (int i = 0; i < 4; i++) {
int[] count = new int[R + 1];
for (int num : nums) {
int c = (num >>> (i * 8)) & bitmask;
count[c + 1]++;
}
for (int r = 0; r < R; r++) count[r + 1] += count[r];
if (i == 3) {
int shift1 = count[R] - count[R/2];
int shift2 = count[R/2];
for (int r = 0; r < R/2; r++)
count[r] += shift1;
for (int r = R/2; r < R; r++)
count[r] -= shift2;
}
for (int num : nums) {
int c = (num >>> (i * 8)) & bitmask;
aux[count[c]++] = num;
}
System.arraycopy(aux, 0, nums, 0, nums.length);
}
return nums[nums.length - k];
}
}
2、堆
采用大顶堆
采用大顶堆装入所有元素,然后删除k-1个元素,剩下的堆顶元素即为所求,时间为O(n+klogn)。
import java.util.PriorityQueue;
import java.util.Collections;
class Solution {
public int findKthLargest3(int[] nums, int k) {
//大顶堆
//PriorityQueue heap = new PriorityQueue(Collections.reverseOrder()); //error
PriorityQueue<Integer> heap = new PriorityQueue<>(Collections.reverseOrder());
//
/*
Queue<Integer> PQ= new PriorityQueue<>(nums.length, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if(o1>o2) return -1;
else if(o1<o2) return 1;
else return 0;
}
} );
*/
for (int i=0; i<nums.length; i++) {
heap.add(nums[i]);
}
for (int i=0; i<k-1; i++) {
heap.poll();
}
return heap.peek(); //error: incompatible types: Object cannot be converted to int
}
}
采用小顶堆
采用小顶堆装入所有元素,同时控制元素个数在k个,当数组遍历完成之后,堆顶元素即为所求,时间为O(n*logk)。
import java.util.PriorityQueue;
import java.util.Collections;
class Solution {
public int findKthLargest4(int[] nums, int k) {
//小顶堆
PriorityQueue<Integer> heap = new PriorityQueue<Integer>(k + 1);
/*
PriorityQueue<Integer> heap =
new PriorityQueue<Integer>((n1, n2) -> n1 - n2);
*/
for(int e : nums) {
heap.add(e);
if (heap.size() > k) {
heap.poll();
}
}
return heap.poll();
}
}
3、快速选择
采用快排的划分方法,平均时间复杂度为O(n),最坏时间复杂度为O(n^2)。
递归版本
主要思路是不断划分数组,如果参照元素后面的元素个数count(包括参照元素)等于k,直接返回参照元素;如果小于k,则令k = k - count,在左边继续找;如果大于k,则k不变,在右边继续找。
class Solution {
public int findKthLargest5(int[] nums, int k) {
//快速选择
return quickSelect(nums, 0, nums.length-1, k);
}
private int quickSelect(int[] nums, int low, int high, int k) {
int pivot = low;
for (int i=low; i<high; i++) {
if (nums[i] <= nums[high]) {
swap(nums, i, pivot++);
}
}
swap(nums, high, pivot);
int count = high - pivot + 1; //大于等于nums[pivot]的元素个数
if (count == k) {
return nums[pivot];
}
if (count > k) {
return quickSelect(nums, pivot+1, high, k); //返回右边部分
}
return quickSelect(nums, low, pivot-1, k-count);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
迭代版本
所求元素在数组排序后的下标为index = nums.length-k(找第k大的元素等价于找第n-k+1小的元素),因此主要思路是不断划分数组,直到找到一个下标等于index的参照元素,将其返回。
class Solution {
public int findKthLargest6(int[] nums, int k) {
k = nums.length - k; //第k大的元素在排序后应该所在的位置
int l = 0, r = nums.length - 1;
while (l <= r) {
int i = l; // partition [l,r] by A[l]: [l,i]<A[l], [i+1,j)>=A[l]
for (int j = l + 1; j <= r; j++) {
if (nums[j] < nums[l]) {
swap(nums, j, ++i);
}
}
swap(nums, l, i); //i-1为最后一个小于参照元素的元素的下标,i为参照元素
if (k < i) r = i - 1;
else if (k > i) l = i + 1;
else return nums[i]; //找到一个下标等于所求下标的参照元素
}
return -1; //k非法的情况
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
将输入数组随机化
由于输入数组可能基本有序以及选取参照元素方式的单一,导致时间可能为O(n^2)。这里先对输入数组打乱所有元素的顺序,当然,也可以在选取参照元素的时候,采用随机元素或者下标为中间的元素。
class Solution {
public int findKthLargest(int[] nums, int k) {
shuffle(nums);
return findK(nums, nums.length - k, 0, nums.length - 1);
}
private int findK(int[] nums, int k, int l, int r) {
int pivotIndex = partition(nums, l, r);
if (k < pivotIndex)
return findK(nums, k, l, pivotIndex - 1);
else if (k > pivotIndex)
return findK(nums, k, pivotIndex + 1, r);
else // k == pivotIndex
return nums[k];
}
private int partition(int[] a, int lo, int hi) {
int pivotIdx = lo; // bad practice
int pivotVal = a[pivotIdx];
// put pivot last
swap(a, pivotIdx, hi);
int storeIdx = lo;
for (int i = lo; i < hi; i++) {
if (a[i] <= pivotVal) {
swap(a, i, storeIdx);
storeIdx++;
}
}
swap(a, storeIdx, hi); //storeIdx+1是第一个大于参照元素的元素,storeIdx为参照元素
return storeIdx;
}
private void shuffle(int[] a) {
for (int i = a.length - 1; i >= 0; i--) {
int j = (int) (Math.random() * (i + 1)); // j=0..i
swap(a, i, j);
}
}
private void shuffle2(int[] a) {
final Random random = new Random();
for(int ind = 1; ind < a.length; ind++) {
final int r = random.nextInt(ind + 1);
exch(a, ind, r);
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
采用中间下标的元素作为参照元素
在区间[left, right]中选取参照元素时,选取的参照元素nums[mid]满足nums[left] <= nums[mid] <= nums[right],可以有效避免最坏情况的出现(参照元素总是选中区间最大值或最小值)。此种方式比前面几种快得多,运行时间可以超过98%的Java提交。
class Solution {
public int findKthLargest8(int[] nums, int k) {
return select(nums, k-1);
}
// Quick select
private int select(int[] nums, int k) {
int left = 0, right = nums.length-1;
while(true) {
if(left == right)
return nums[left];
int pivotIndex = medianOf3(nums, left, right);
pivotIndex = partition(nums, left, right, pivotIndex);
if(pivotIndex == k)
return nums[k];
else if(pivotIndex > k)
right = pivotIndex-1;
else
left = pivotIndex+1;
}
}
//Use median-of-three strategy to choose pivot,很强大的选取参照元素操作的优化方式
private int medianOf3(int[] nums, int left, int right) {
//让nums[right]<=nums[mid]<=nums[left]
int mid = left + (right - left) / 2;
if(nums[right] > nums[left])
swap(nums, left, right);
if(nums[right] > nums[mid])
swap(nums, right, mid);
if(nums[mid] > nums[left])
swap(nums,left, mid);
//返回mid
return mid;
}
private int partition(int[] nums, int left, int right, int pivotIndex) {
int pivotValue = nums[pivotIndex];
swap(nums, pivotIndex, right);
int index = left;
for(int i = left; i < right; ++i) {
if(nums[i] > pivotValue) {
swap(nums, index, i);
++index;
}
}
swap(nums, right, index);
return index;
}
}
采用BFPTR算法对参照元素的选取进行优化
通过选取中位数的中位数作为参照元素,来避免单一地选取最低/最高下标的元素所导致的最坏情况(O(n^2))的发生。具体来说,把元素分为元素个数为5的小组(最后一组元素个数可能为n mod 5),分别进行插入排序。将各个小组的中位数移到数组前面,然后重复以上操作,直到只剩一个元素,将其作为参照元素。
class Solution {
public int findKthLargest(int[] nums, int k) {
return bfprt(nums, 0, nums.length-1, k);
}
private int bfprt(int[] a, int l, int r, int k) {
int p = findMid(a, l, r); //寻找中位数的中位数
int i = partition111(a, l, r, p);
//int m = i - l + 1;
int m = r - i + 1;
if (m == k) {
return a[i];
}
if (m > k) {
//return bfprt(a, l, i-1, k);
return bfprt(a, i+1, r, k);
}
//return bfprt(a, i+1, r, k-m);
return bfprt(a, l, i-1, k-m);
}
private int findMid(int[] a, int l, int r) {
if (l == r) {
return l;
}
//处理前几组元素
int i = 0, n = 0;
for (i=l; i<r-5; i+=5) {
insertionSort(a, i, i+4);
n = i - l;
swap(a, l+n/5, i+2);
}
//处理最后一组元素(剩余元素)
int num = r - i + 1;
if (num > 0) {
insertionSort(a, i, i+num-1);
n = i - l;
swap(a, l+n/5, i+num/2);
}
n /= 5;
if (n == 0) {
return l;
}
return findMid(a, l, l + n);
}
private void insertionSort(int[] a, int l, int r) {
for (int i=l+1; i<=r; i++) {
if (a[i-1] > a[i]) {
int temp = a[i];
int j = i;
while (i>i && a[j-1]>temp) {
a[j] = a[j-1];
j--;
}
a[j] = temp;
}
}
}
private int partition(int[] a, int l, int r, int p) {
swap(a, l, p);
int pivot = a[l];
int i = l, j = r;
while (i < j) {
while (a[j]>pivot && i<j) {
j--;
}
a[i] = a[j];
while (a[i]<=pivot && i<j) {
i++;
}
a[j] = a[i];
}
a[i] = pivot;
return i;
}
}
参考:
https://zhuanlan.zhihu.com/p/31498036
https://blog.csdn.net/laojiu_/article/details/54986553
https://www.jianshu.com/p/495e5019669c