题目链接

数组中第K个最大元素

题目描述

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

解题思路

1. 直接排序

很明显,如果数组是有序的,那么数组索引下标为 nums.length - k 的元素就是数组中第 K 个最大元素。

注意:在面试中遇到此问题肯定不是考这种解决方法。

  1. public int findKthLargest(int[] nums, int k) {
  2. if (nums == null || nums.length == 0){
  3. return 0;
  4. }
  5. int len = nums.length;
  6. Arrays.sort(nums);
  7. return nums[len - k];
  8. }
  • 时间复杂度LeetCode215. 数组中第 K 个最大元素 - 图1,取决于对数组进行排序的排序算法的时间复杂度。
  • 空间复杂度LeetCode215. 数组中第 K 个最大元素 - 图2

    2. 堆结构

利用堆排序解决——建立一个大根堆,做 k - 1 次删除操作后堆顶元素就是数组中的第 K 个最大元素。

注意

  • 在 Java 中可以直接使用优先队列 PriorityQueue 作为大根堆,但是在面试中,使用该方法更多考察的对堆结构的实现。(下面给出使用优先队列的代码实现,但不推荐)
  • 需要掌握「建堆」、「调整堆」和「删除」过程。

    优先队列——最大堆

使用优先队列作为大根堆的代码实现:

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
  4. public int compare(Integer a, Integer b) {
  5. return b - a;
  6. }
  7. });
  8. for (int i = 0; i < nums.length; i++) {
  9. maxHeap.add(nums[i]);
  10. }
  11. // 做 k - 1 次删除操作
  12. for (int i = 0; i < k - 1; i++) {
  13. maxHeap.poll();
  14. }
  15. return maxHeap.peek();
  16. }
  17. }

构建最大堆

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. int heapSize = nums.length;
  4. // 构建堆
  5. for (int i = heapSize / 2 - 1; i >= 0; i--) {
  6. downAdjust(nums, i, heapSize);
  7. }
  8. // 做 k - 1 次删除操作,每次删除后调整堆
  9. for (int i = nums.length - 1; i >= nums.length - k + 1; i--) {
  10. swap(nums, 0, i);
  11. downAdjust(nums, 0, i);
  12. }
  13. return nums[0];
  14. }
  15. public void downAdjust(int[] nums, int index, int heapSize) {
  16. int cIndex = index * 2 + 1;
  17. while (cIndex < heapSize) {
  18. if (cIndex + 1 < heapSize && nums[cIndex + 1] > nums[cIndex]) {
  19. cIndex++;
  20. }
  21. if (nums[index] > nums[cIndex]) {
  22. break;
  23. }
  24. swap(nums, index, cIndex);
  25. index = cIndex;
  26. cIndex = index * 2 + 1;
  27. }
  28. }
  29. public void swap(int[] nums, int i, int j) {
  30. int temp = nums[i];
  31. nums[i] = nums[j];
  32. nums[j] = temp;
  33. }
  34. }
  • 时间复杂度LeetCode215. 数组中第 K 个最大元素 - 图3,建堆的时间代价是LeetCode215. 数组中第 K 个最大元素 - 图4,删除的总代价是LeetCode215. 数组中第 K 个最大元素 - 图5
  • 空间复杂度LeetCode215. 数组中第 K 个最大元素 - 图6

    优先队列——最小堆

其实只需要建立大小为 K 的最小堆就可以找到数组中的第 K 个最大元素。遍历数组,当数组元素大于栈顶元素就将栈顶元素删除并把该元素加入堆中,遍历结束后栈顶元素就是第 K 大元素。

image.png

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, new Comparator<>() {
  4. public int compare(Integer a, Integer b) {
  5. return a - b;
  6. }
  7. });
  8. for (int i = 0; i < k; i++) {
  9. minHeap.add(nums[i]);
  10. }
  11. for (int i = k; i < nums.length; i++) {
  12. // 当前元素大于堆顶元素
  13. if (nums[i] > minHeap.peek()) {
  14. minHeap.poll();
  15. minHeap.add(nums[i]);
  16. }
  17. }
  18. // 最后堆顶元素就是第 K 大
  19. return minHeap.poll();
  20. }
  21. }
  • 时间复杂度LeetCode215. 数组中第 K 个最大元素 - 图8,堆中只存在 K 个元素。
  • 空间复杂度LeetCode215. 数组中第 K 个最大元素 - 图9

还可以对上述代码进行优化,只使用一个 for 循环来完成。

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, new Comparator<>() {
  4. public int compare(Integer a, Integer b) {
  5. return a - b;
  6. }
  7. });
  8. for (int num : nums) {
  9. // 堆中已经有 k 各元素并且当前元素比堆顶元素小,那就不添加
  10. if (minHeap.size() > k && num < minHeap.peek()) continue;
  11. minHeap.add(num);
  12. if (minHeap.size() > k) {
  13. minHeap.poll();
  14. }
  15. }
  16. return minHeap.poll();
  17. }
  18. }

构建最小堆

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. for (int i = k / 2 - 1; i >= 0; i--) {
  4. downAdjust(nums, i, k);
  5. }
  6. for (int i = k; i < nums.length; i++) {
  7. if (nums[i] < nums[0]) continue;
  8. swap(nums, i, 0);
  9. downAdjust(nums, 0, k);
  10. }
  11. return nums[0];
  12. }
  13. public void downAdjust(int[] nums, int index, int size) {
  14. int cIndex = index * 2 + 1;
  15. while (cIndex < size) {
  16. if (cIndex + 1 < size && nums[cIndex + 1] < nums[cIndex]) {
  17. cIndex++;
  18. }
  19. if (nums[index] < nums[cIndex]) {
  20. break;
  21. }
  22. swap(nums, index, cIndex);
  23. index = cIndex;
  24. cIndex = index * 2 + 1;
  25. }
  26. }
  27. public void swap(int[] nums, int i, int j) {
  28. int temp = nums[i];
  29. nums[i] = nums[j];
  30. nums[j] = temp;
  31. }
  32. }

基于快速排序的选择算法

算过流程:

设数组长度为 length。快速排序中,通过 partition 函数,将一个基准元素(假设下标为 j)划分成以下两个部分:

  • nums[0]...nums[j - 1] 区间内的所有元素都小于等于 nums[j]
  • nums[j + 1]...nums[length - 1] 区间内的所有元素都大于等于 nums[j]

经过一次划分,一定可以确定一个元素的最终位置,假设对于 nums[j] 的最终位置为 m。而数组中第 K 个最大元素的下标为 length - K,当我们某一次划分后确定了某一个元素的位置等于 length - K时,就说明找到了数组中第 K 个最大元素。

注意:对于极端用例可能会导致快速排序时间复杂度退化至 LeetCode215. 数组中第 K 个最大元素 - 图10,所以最好随机选择基准元素。

  1. class Solution {
  2. public int findKthLargest(int[] nums, int k) {
  3. int target = nums.length - k;
  4. return quickSelect(nums, 0, nums.length - 1, target);
  5. }
  6. public int quickSelect(int[] nums, int lo, int hi, int target) {
  7. if (lo > hi) {
  8. return nums[lo];
  9. }
  10. int rand = lo + (int)(Math.random() * (hi - lo + 1));
  11. swap(nums, rand, lo);
  12. int pivotIndex = partition(nums, lo, hi);
  13. if (pivotIndex == target) {
  14. return nums[pivotIndex];
  15. } else if (target > pivotIndex) {
  16. return quickSelect(nums, pivotIndex + 1, hi, target);
  17. } else {
  18. return quickSelect(nums, lo, pivotIndex - 1, target);
  19. }
  20. }
  21. public int partition(int[] nums, int lo, int hi) {
  22. int left = lo;
  23. int right = hi;
  24. int pivot = nums[lo];
  25. while (left < right) {
  26. while (left < right && nums[right] > pivot) {
  27. right--;
  28. }
  29. while (left < right && nums[left] <= pivot) {
  30. left++;
  31. }
  32. if (left < right) {
  33. swap(nums, left, right);
  34. }
  35. }
  36. nums[lo] = nums[left];
  37. nums[left] = pivot;
  38. return left;
  39. }
  40. public void swap(int[] nums, int i, int j) {
  41. int temp = nums[i];
  42. nums[i] = nums[j];
  43. nums[j] = temp;
  44. }
  45. }
  • 时间复杂度LeetCode215. 数组中第 K 个最大元素 - 图11
  • 空间复杂度LeetCode215. 数组中第 K 个最大元素 - 图12