算法模式:快速选择

算法模式:快速选择

在上一篇文章 算法模式:Top K 问题 介绍了如何利用堆快速选出最 X 的 K 个元素。本篇文章,介绍一种可以快速选择第 K 个 最 X 元素的算法模式:快速选择。

快速选择

快速选择起源于快排算法。在快排算法中,把元素根据基准元素分成左右两部分,一边的元素小于基准元素,另外一个的元素大于等于基准元素,再对两边的元素递归处理,最终得到有序结果。受此启发,在将元素根据基准元素分成左右两部分后,这里假设,左边小于基准元素,右边大于等于基准元素,那么会有如下三种情况:

  1. 当前基准元素所在位置正好是 K,正好是所求结果,直接返回;

  2. 当前基准元素所在位置小于 K,那么 K 位置在当前基准元素的右边;

  3. 当前基准元素所在位置大于 K,那么 K 位置在当前基准元素的左边;

所以,该模式不仅适用于求第 K 个之最元素,也适用于求“Top K 问题”。

LeetCode 215. 数组中的第K个最大元素

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

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

你必须设计并实现时间复杂度为 \$O(n)\$ 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105

  • -104 <= nums[i] <= 104

思路分析

一道典型的快速排序题目。可以利用快速选择,高效锁点答案。代码如下:

/**
 * @author D瓜哥 · https://www.diguage.com
 * @since 2025-03-26 16:24:19
 */
public int findKthLargest(int[] nums, int k) {
  // 第 k 大的元素,正好是升序数组的 nums.length - k 个元素
  return quickselect(nums, nums.length - k, 0, nums.length - 1);
}

private int quickselect(int[] nums, int k, int left, int right) {
  if (left == right) {
    return nums[k];
  }
  int pivot = nums[left];
  // 由于下面先移动指针,所以左右指针各项左右移一位
  int l = left - 1, r = right + 1;
  while (l < r) {
    // 交换之后,没有移动指针,所以先移动指针再循环
    do {
      l++;
    } while (nums[l] < pivot);
    do {
      r--;
    } while (pivot < nums[r]);
    if (l < r) {
      int tmp = nums[l];
      nums[l] = nums[r];
      nums[r] = tmp;
    }
  }
  // r 是最后一个小于或等于 pivot 的元素的索引。
  // [left, r] 是确定小于或等于 pivot 的部分。
  if (k <= r) {
    return quickselect(nums, k, left, r);
  } else {
    return quickselect(nums, k, r + 1, right);
  }
}