题目描述

image.png

方法一:利用库函数直接排序

  1. import java.util.*;
  2. public class Finder {
  3. public int findKth(int[] a, int n, int K) {
  4. Arrays.sort(a);
  5. return a[n-K]; //n-K 为排序数组后数组中第K大的值的下标
  6. }
  7. }

方法二:利用快排思想

  1. import java.util.*;
  2. public class Finder {
  3. public int findKth(int[] a, int n, int K) {
  4. int left = 0, right = n-1;
  5. //转换成目标值在数组中的索引
  6. int target = n-K;
  7. while(true){
  8. int index = partition(a,left,right);
  9. if(index == target)
  10. return a[index];
  11. //a[index] 为数组中第index大的值
  12. else if(index < target)
  13. left = index+1;
  14. else
  15. right = index-1;
  16. }
  17. }
  18. public int partition(int[] a, int left, int right){
  19. //以该值为切片
  20. int pivot = a[left];
  21. int j = left;
  22. for(int i = left+1; i <= right; i++){
  23. //如果切片右边的数小于它,则把小的值放在切片的右邻
  24. if(a[i] < pivot){
  25. j++;
  26. swap(a,j,i);
  27. }
  28. }
  29. //此时上面循环算出来后区间[left+1,j]<pivot && [j+1,right]>pivot
  30. //下面我们交换left和j的值
  31. swap(a,left,j); //此时[left,j-1]<pivot && a[j]==pivot && [j+1,right]>pivot
  32. return j; //此时因为pivot右边的值都比它大,左边的值都比它小,所以返回它的下标回到主程序判断是否为目标值的下标
  33. }
  34. public void swap(int[] a, int j, int i){
  35. int temp = a[j];
  36. a[j] = a[i];
  37. a[i] = temp;
  38. }
  39. }

注意:本题必须随机初始化 pivot 元素,否则通过时间会很慢,因为测试用例中有极端测试用例。

为了应对极端测试用例,使得递归树加深,可以在循环一开始的时候,随机交换第 11 个元素与它后面的任意 11 个元素的位置;说明:最极端的是顺序数组与倒序数组,此时递归树画出来是链表,时间复杂度是 O(N^2),根本达不到减治的效果。
优化后的代码

  1. import java.util.*;
  2. public class Finder {
  3. private static Random random = new Random(System.currentTimeMillis());
  4. public int findKth(int[] a, int n, int K) {
  5. int left = 0, right = n-1;
  6. //转换成目标值在数组中的索引
  7. int target = n-K;
  8. while(true){
  9. int index = partition(a,left,right);
  10. if(index == target)
  11. return a[index];
  12. //a[index] 为数组中第index大的值
  13. else if(index < target)
  14. left = index+1;
  15. else
  16. right = index-1;
  17. }
  18. }
  19. public int partition(int[] a, int left, int right){
  20. //以一个随机下标对应的数组值为切片
  21. if(right>left){
  22. int randomIndex = left+1+random.nextInt(right-left);
  23. swap(a,left,randomIndex);
  24. }
  25. int pivot = a[left];
  26. int j = left;
  27. for(int i = left+1; i <= right; i++){
  28. //如果切片右边的数小于它,则把小的值放在切片的右邻
  29. if(a[i] < pivot){
  30. j++;
  31. swap(a,j,i);
  32. }
  33. }
  34. //此时上面循环算出来后区间[left+1,j]<pivot && [j+1,right]>pivot
  35. //下面我们交换left和j的值
  36. swap(a,left,j); //此时[left,j-1]<pivot && a[j]==pivot && [j+1,right]>pivot
  37. return j; //此时因为pivot右边的值都比它大,左边的值都比它小,所以返回它的下标回到主程序判断是否为目标值的下标
  38. }
  39. public void swap(int[] a, int j, int i){
  40. int temp = a[j];
  41. a[j] = a[i];
  42. a[i] = temp;
  43. }
  44. }

方法三:使用最小堆(最大堆)

思路:将全部的值放进一个最小堆中,此时poll出来n-K个值,然后堆顶的值就是我们想要的值了(最小堆)
poll出来K-1个值,然后堆顶就是我们想要的值了(最大堆)
最小堆的

  1. import java.util.*;
  2. public class Finder {
  3. public int findKth(int[] a, int n, int K) {
  4. //使用最小堆 默认是最小堆a-b
  5. PriorityQueue<Integer> minHeap = new PriorityQueue<>(n,(a1,b)-> a1-b);
  6. for(int num:a){
  7. minHeap.add(num);
  8. }
  9. for(int i = 0; i < n-K; i++){
  10. minHeap.poll();
  11. }
  12. return minHeap.peek();
  13. }
  14. }

最大堆的

  1. import java.util.*;
  2. public class Finder {
  3. public int findKth(int[] a, int n, int K) {
  4. // write code here
  5. //使用最小堆 默认是最小堆 现在是b-a1 所以为最大堆
  6. PriorityQueue<Integer> minHeap = new PriorityQueue<>(n,(a1,b)-> b-a1);
  7. for(int num:a){
  8. minHeap.add(num);
  9. }
  10. for(int i = 0; i < K-1; i++){
  11. minHeap.poll();
  12. }
  13. return minHeap.peek();
  14. }
  15. }