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

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

题目:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

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

示例 2:


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

题解:
运用快速排序的方法:
首先我们来回顾一下快速排序,这是一个典型的分治算法。我们对数组 a [ l ⋯ r ] a[l⋯r] a[lr]做快速排序的过程是:

  • 分解: 将数组 a [ l ⋯ r ] a[l⋯r] a[lr] 「划分」成两个子数组 a [ l ⋯ q − 1 ] 、 a [ q + 1 ⋯ r ] a[l⋯q−1]、a[q+1⋯r] a[lq1]a[q+1r],使得 a [ l ⋯ q − 1 ] a[l⋯q−1] a[lq1] 中的每个元素小于等于 a [ q ] a[q] a[q],且 a [ q ] a[q] a[q] 小于等于 a [ q + 1 ⋯ r ] a[q+1⋯r] a[q+1r] 中的每个元素。其中,计算下标 q q q 也是「划分」过程的一部分。
  • 解决: 通过递归调用快速排序,对子数组 a [ l ⋯ q − 1 ] a[l⋯q−1] a[lq1] a [ q + 1 ⋯ r ] a[q+1⋯r] a[q+1r] 进行排序。
  • 合并: 因为子数组都是原址排序的,所以不需要进行合并操作, a [ l ⋯ r ] a[l⋯r] a[lr] 已经有序。

上文中提到的 「划分」 过程是:从子数组 a [ l ⋯ r ] a[l⋯r] a[lr] 中选择任意一个元素 x x x 作为主元,调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x x x 的最终位置就是 q q q
由此可以发现每次经过「划分」操作后,我们一定可以确定一个元素的最终位置,即 x x x 的最终位置为 q q q,并且保证 a [ l ⋯ q − 1 ] a[l⋯q−1] a[lq1] 中的每个元素小于等于 a [ q ] a[q] a[q],且 a [ q ] a[q] a[q] 小于等于 a [ q + 1 ⋯ r ] a[q+1⋯r] a[q+1r] 中的每个元素。所以只要某次划分的 q q q 为倒数第 k k k 个下标的时候,我们就已经找到了答案。 我们只关心这一点,至于 a [ l ⋯ q − 1 ] a[l⋯q−1] a[lq1] a [ q + 1 ⋯ r ] a[q+1⋯r] a[q+1r] 是否是有序的,我们不关心。

因此我们可以改进快速排序算法来解决这个问题:在分解的过程当中,我们会对子数组进行划分,如果划分得到的 q q q 正好就是我们需要的下标,就直接返回 a [ q ] a[q] a[q];否则,如果 q q q 比目标下标小,就递归右子区间,否则递归左子区间。这样就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。这就是「快速选择」算法。

我们知道快速排序的性能和「划分」出的子数组的长度密切相关。直观地理解如果每次规模为 n n n 的问题我们都划分成 1 1 1 n − 1 n−1 n1,每次递归的时候又向 n − 1 n−1 n1 的集合中递归,这种情况是最坏的,时间代价是 O ( n 2 ) O(n ^ 2) O(n2)。我们可以引入随机化来加速这个过程,它的时间代价的期望是 O ( n ) O(n) O(n)

java代码:

 public static int findKthLargest(int[] nums, int k) {
        int len = nums.length;

        return quickSelect(nums, 0, len - 1, len - k);

    }

    private static int quickSelect(int[] nums, int low, int high, int k) {
        if (nums.length == 0 || low < 0 || high > nums.length - 1 || low > high) return 0;
        int i = low;
        int j = high;
        int base = nums[i];
        while (i < j) {
            while (base <= nums[j]) {
                j--;
            }
            while (base >= nums[i]) {
                i++;
            }

            if (i < j) {
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j] = tmp;
            }
        }

        nums[low] = nums[j];
        nums[j] = base;

        if (j > k) {
            return quickSelect(nums, low, j - 1, k);
        } else if (j < k) {
            return quickSelect(nums, j + 1, high, k);
        } else {
            return base;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值