leetcode 215. Kth Largest Element in an Array

小鱼儿 2023-07-05 05:25 77阅读 0赞

目录

一、问题分析

二、代码实现

1、排序

采用内置排序

采用冒泡排序

基数排序

2、堆

采用大顶堆

采用小顶堆

3、快速选择

递归版本

迭代版本

将输入数组随机化

采用中间下标的元素作为参照元素

采用BFPTR算法对参照元素的选取进行优化


https://leetcode.com/problems/kth-largest-element-in-an-array/

返回数组中第k大的元素

一、问题分析

  1. Input: [3,2,3,1,2,4,5,5,6] and k = 4Output: 4

输出的是第k大的元素,注意不是第k大的不同元素。另外,k总是大于等于1。

二、代码实现

1、排序

采用内置排序

采用Arrays.sort(),然后返回nums[nums.length-k]即可,时间为O(nlogn)。

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. class Solution {
  4. public int findKthLargest1(int[] nums, int k) {
  5. //排序
  6. Arrays.sort(nums);
  7. return nums[nums.length-k];
  8. //Arrays.sort(nums, Collections.reverseOrder());
  9. //return nums[k - 1];
  10. }
  11. }

采用冒泡排序

采用用冒泡的方式,进行k轮冒泡。

  1. class Solution {
  2. public int findKthLargest1(int[] nums, int k) {
  3. //冒泡
  4. for (int i=0; i<k; i++) { //控制冒泡的轮数为k
  5. for (int j=0; j<nums.length-1; j++) {
  6. if (nums[j] > nums[j+1]) {
  7. int temp = nums[j];
  8. nums[j] = nums[j+1];
  9. nums[j+1] = temp;
  10. }
  11. }
  12. }
  13. return nums[nums.length-k];
  14. }
  15. }

基数排序

  1. class Solution {
  2. //LSD radix sort
  3. public int findKthLargest9(int[] nums, int k) {
  4. final int R = (1 << 8);
  5. final int bitmask = R - 1;
  6. int[] aux = new int[nums.length];
  7. for (int i = 0; i < 4; i++) {
  8. int[] count = new int[R + 1];
  9. for (int num : nums) {
  10. int c = (num >>> (i * 8)) & bitmask;
  11. count[c + 1]++;
  12. }
  13. for (int r = 0; r < R; r++) count[r + 1] += count[r];
  14. if (i == 3) {
  15. int shift1 = count[R] - count[R/2];
  16. int shift2 = count[R/2];
  17. for (int r = 0; r < R/2; r++)
  18. count[r] += shift1;
  19. for (int r = R/2; r < R; r++)
  20. count[r] -= shift2;
  21. }
  22. for (int num : nums) {
  23. int c = (num >>> (i * 8)) & bitmask;
  24. aux[count[c]++] = num;
  25. }
  26. System.arraycopy(aux, 0, nums, 0, nums.length);
  27. }
  28. return nums[nums.length - k];
  29. }
  30. }

2、堆

采用大顶堆

采用大顶堆装入所有元素,然后删除k-1个元素,剩下的堆顶元素即为所求,时间为O(n+klogn)。

  1. import java.util.PriorityQueue;
  2. import java.util.Collections;
  3. class Solution {
  4. public int findKthLargest3(int[] nums, int k) {
  5. //大顶堆
  6. //PriorityQueue heap = new PriorityQueue(Collections.reverseOrder()); //error
  7. PriorityQueue<Integer> heap = new PriorityQueue<>(Collections.reverseOrder());
  8. //
  9. /*
  10. Queue<Integer> PQ= new PriorityQueue<>(nums.length, new Comparator<Integer>() {
  11. @Override
  12. public int compare(Integer o1, Integer o2) {
  13. if(o1>o2) return -1;
  14. else if(o1<o2) return 1;
  15. else return 0;
  16. }
  17. } );
  18. */
  19. for (int i=0; i<nums.length; i++) {
  20. heap.add(nums[i]);
  21. }
  22. for (int i=0; i<k-1; i++) {
  23. heap.poll();
  24. }
  25. return heap.peek(); //error: incompatible types: Object cannot be converted to int
  26. }
  27. }

采用小顶堆

采用小顶堆装入所有元素,同时控制元素个数在k个,当数组遍历完成之后,堆顶元素即为所求,时间为O(n*logk)。

  1. import java.util.PriorityQueue;
  2. import java.util.Collections;
  3. class Solution {
  4. public int findKthLargest4(int[] nums, int k) {
  5. //小顶堆
  6. PriorityQueue<Integer> heap = new PriorityQueue<Integer>(k + 1);
  7. /*
  8. PriorityQueue<Integer> heap =
  9. new PriorityQueue<Integer>((n1, n2) -> n1 - n2);
  10. */
  11. for(int e : nums) {
  12. heap.add(e);
  13. if (heap.size() > k) {
  14. heap.poll();
  15. }
  16. }
  17. return heap.poll();
  18. }
  19. }

3、快速选择

采用快排的划分方法,平均时间复杂度为O(n),最坏时间复杂度为O(n^2)。

递归版本

主要思路是不断划分数组,如果参照元素后面的元素个数count(包括参照元素)等于k,直接返回参照元素;如果小于k,则令k = k - count,在左边继续找;如果大于k,则k不变,在右边继续找。

  1. class Solution {
  2. public int findKthLargest5(int[] nums, int k) {
  3. //快速选择
  4. return quickSelect(nums, 0, nums.length-1, k);
  5. }
  6. private int quickSelect(int[] nums, int low, int high, int k) {
  7. int pivot = low;
  8. for (int i=low; i<high; i++) {
  9. if (nums[i] <= nums[high]) {
  10. swap(nums, i, pivot++);
  11. }
  12. }
  13. swap(nums, high, pivot);
  14. int count = high - pivot + 1; //大于等于nums[pivot]的元素个数
  15. if (count == k) {
  16. return nums[pivot];
  17. }
  18. if (count > k) {
  19. return quickSelect(nums, pivot+1, high, k); //返回右边部分
  20. }
  21. return quickSelect(nums, low, pivot-1, k-count);
  22. }
  23. private void swap(int[] nums, int i, int j) {
  24. int temp = nums[i];
  25. nums[i] = nums[j];
  26. nums[j] = temp;
  27. }
  28. }

迭代版本

所求元素在数组排序后的下标为index = nums.length-k(找第k大的元素等价于找第n-k+1小的元素),因此主要思路是不断划分数组,直到找到一个下标等于index的参照元素,将其返回。

  1. class Solution {
  2. public int findKthLargest6(int[] nums, int k) {
  3. k = nums.length - k; //第k大的元素在排序后应该所在的位置
  4. int l = 0, r = nums.length - 1;
  5. while (l <= r) {
  6. int i = l; // partition [l,r] by A[l]: [l,i]<A[l], [i+1,j)>=A[l]
  7. for (int j = l + 1; j <= r; j++) {
  8. if (nums[j] < nums[l]) {
  9. swap(nums, j, ++i);
  10. }
  11. }
  12. swap(nums, l, i); //i-1为最后一个小于参照元素的元素的下标,i为参照元素
  13. if (k < i) r = i - 1;
  14. else if (k > i) l = i + 1;
  15. else return nums[i]; //找到一个下标等于所求下标的参照元素
  16. }
  17. return -1; //k非法的情况
  18. }
  19. private void swap(int[] nums, int i, int j) {
  20. int temp = nums[i];
  21. nums[i] = nums[j];
  22. nums[j] = temp;
  23. }
  24. }

将输入数组随机化

由于输入数组可能基本有序以及选取参照元素方式的单一,导致时间可能为O(n^2)。这里先对输入数组打乱所有元素的顺序,当然,也可以在选取参照元素的时候,采用随机元素或者下标为中间的元素。

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. shuffle(nums);
  4. return findK(nums, nums.length - k, 0, nums.length - 1);
  5. }
  6. private int findK(int[] nums, int k, int l, int r) {
  7. int pivotIndex = partition(nums, l, r);
  8. if (k < pivotIndex)
  9. return findK(nums, k, l, pivotIndex - 1);
  10. else if (k > pivotIndex)
  11. return findK(nums, k, pivotIndex + 1, r);
  12. else // k == pivotIndex
  13. return nums[k];
  14. }
  15. private int partition(int[] a, int lo, int hi) {
  16. int pivotIdx = lo; // bad practice
  17. int pivotVal = a[pivotIdx];
  18. // put pivot last
  19. swap(a, pivotIdx, hi);
  20. int storeIdx = lo;
  21. for (int i = lo; i < hi; i++) {
  22. if (a[i] <= pivotVal) {
  23. swap(a, i, storeIdx);
  24. storeIdx++;
  25. }
  26. }
  27. swap(a, storeIdx, hi); //storeIdx+1是第一个大于参照元素的元素,storeIdx为参照元素
  28. return storeIdx;
  29. }
  30. private void shuffle(int[] a) {
  31. for (int i = a.length - 1; i >= 0; i--) {
  32. int j = (int) (Math.random() * (i + 1)); // j=0..i
  33. swap(a, i, j);
  34. }
  35. }
  36. private void shuffle2(int[] a) {
  37. final Random random = new Random();
  38. for(int ind = 1; ind < a.length; ind++) {
  39. final int r = random.nextInt(ind + 1);
  40. exch(a, ind, r);
  41. }
  42. }
  43. private void swap(int[] nums, int i, int j) {
  44. int temp = nums[i];
  45. nums[i] = nums[j];
  46. nums[j] = temp;
  47. }
  48. }

采用中间下标的元素作为参照元素

在区间[left, right]中选取参照元素时,选取的参照元素nums[mid]满足nums[left] <= nums[mid] <= nums[right],可以有效避免最坏情况的出现(参照元素总是选中区间最大值或最小值)。此种方式比前面几种快得多,运行时间可以超过98%的Java提交。

  1. class Solution {
  2. public int findKthLargest8(int[] nums, int k) {
  3. return select(nums, k-1);
  4. }
  5. // Quick select
  6. private int select(int[] nums, int k) {
  7. int left = 0, right = nums.length-1;
  8. while(true) {
  9. if(left == right)
  10. return nums[left];
  11. int pivotIndex = medianOf3(nums, left, right);
  12. pivotIndex = partition(nums, left, right, pivotIndex);
  13. if(pivotIndex == k)
  14. return nums[k];
  15. else if(pivotIndex > k)
  16. right = pivotIndex-1;
  17. else
  18. left = pivotIndex+1;
  19. }
  20. }
  21. //Use median-of-three strategy to choose pivot,很强大的选取参照元素操作的优化方式
  22. private int medianOf3(int[] nums, int left, int right) {
  23. //让nums[right]<=nums[mid]<=nums[left]
  24. int mid = left + (right - left) / 2;
  25. if(nums[right] > nums[left])
  26. swap(nums, left, right);
  27. if(nums[right] > nums[mid])
  28. swap(nums, right, mid);
  29. if(nums[mid] > nums[left])
  30. swap(nums,left, mid);
  31. //返回mid
  32. return mid;
  33. }
  34. private int partition(int[] nums, int left, int right, int pivotIndex) {
  35. int pivotValue = nums[pivotIndex];
  36. swap(nums, pivotIndex, right);
  37. int index = left;
  38. for(int i = left; i < right; ++i) {
  39. if(nums[i] > pivotValue) {
  40. swap(nums, index, i);
  41. ++index;
  42. }
  43. }
  44. swap(nums, right, index);
  45. return index;
  46. }
  47. }

采用BFPTR算法对参照元素的选取进行优化

通过选取中位数的中位数作为参照元素,来避免单一地选取最低/最高下标的元素所导致的最坏情况(O(n^2))的发生。具体来说,把元素分为元素个数为5的小组(最后一组元素个数可能为n mod 5),分别进行插入排序。将各个小组的中位数移到数组前面,然后重复以上操作,直到只剩一个元素,将其作为参照元素。

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. return bfprt(nums, 0, nums.length-1, k);
  4. }
  5. private int bfprt(int[] a, int l, int r, int k) {
  6. int p = findMid(a, l, r); //寻找中位数的中位数
  7. int i = partition111(a, l, r, p);
  8. //int m = i - l + 1;
  9. int m = r - i + 1;
  10. if (m == k) {
  11. return a[i];
  12. }
  13. if (m > k) {
  14. //return bfprt(a, l, i-1, k);
  15. return bfprt(a, i+1, r, k);
  16. }
  17. //return bfprt(a, i+1, r, k-m);
  18. return bfprt(a, l, i-1, k-m);
  19. }
  20. private int findMid(int[] a, int l, int r) {
  21. if (l == r) {
  22. return l;
  23. }
  24. //处理前几组元素
  25. int i = 0, n = 0;
  26. for (i=l; i<r-5; i+=5) {
  27. insertionSort(a, i, i+4);
  28. n = i - l;
  29. swap(a, l+n/5, i+2);
  30. }
  31. //处理最后一组元素(剩余元素)
  32. int num = r - i + 1;
  33. if (num > 0) {
  34. insertionSort(a, i, i+num-1);
  35. n = i - l;
  36. swap(a, l+n/5, i+num/2);
  37. }
  38. n /= 5;
  39. if (n == 0) {
  40. return l;
  41. }
  42. return findMid(a, l, l + n);
  43. }
  44. private void insertionSort(int[] a, int l, int r) {
  45. for (int i=l+1; i<=r; i++) {
  46. if (a[i-1] > a[i]) {
  47. int temp = a[i];
  48. int j = i;
  49. while (i>i && a[j-1]>temp) {
  50. a[j] = a[j-1];
  51. j--;
  52. }
  53. a[j] = temp;
  54. }
  55. }
  56. }
  57. private int partition(int[] a, int l, int r, int p) {
  58. swap(a, l, p);
  59. int pivot = a[l];
  60. int i = l, j = r;
  61. while (i < j) {
  62. while (a[j]>pivot && i<j) {
  63. j--;
  64. }
  65. a[i] = a[j];
  66. while (a[i]<=pivot && i<j) {
  67. i++;
  68. }
  69. a[j] = a[i];
  70. }
  71. a[i] = pivot;
  72. return i;
  73. }
  74. }

参考:

https://zhuanlan.zhihu.com/p/31498036

https://blog.csdn.net/laojiu_/article/details/54986553

https://www.jianshu.com/p/495e5019669c

发表评论

表情:
评论列表 (有 0 条评论,77人围观)

还没有评论,来说两句吧...

相关阅读