刷题系列之215. 数组中的第K个最大元素

260 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

前言

题目来源

leetcode.cn/problems/kt…

题目介绍

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

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

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

题目分析

根据题目意思,是一个排序后的数组,寻找第k个最大的元素。划重点这个数组是排序过的,可以升序或者降序,假设[3,2,1,5,6,4] 数组按升序[1,2,3,4,5,6] 、降序[6,5,4,3,2,1] 只有2种顺序,要寻找第2个最大的元素,那么第2个可能是2 也可能是5,那么一对比当然是选择5,所以最大的是5。

题目解答

    public int findKthLargest(int[] nums, int k) {
        int numSize=nums.length;
        int j=0;
        quicksort(nums,0,numSize-1);
        return nums[numSize-k];
    }

    public void quicksort(int[] arr, int left, int right) {
        if (left < right) {
            int temp = qsort(arr,left,right);
            /*
            分区,以基准数为中心分为左右两区
            qsort方法的返回值是left,即每次排序完成后基准数的位置
             */
            quicksort(arr,left,temp-1);
            quicksort(arr,temp+1,right);
        }

    }

    /**
     * 排序过程
     * arr 待排数组
     * left 待排数组的最小下标
     * right 待排数组的最大下标
     */
    private static int qsort(int[] arr, int left, int right) {
        int temp = arr[left];
        while (right>left){
            //让跳出循环时则表示该数小于基准数,所以转到if中
            //如果右边的值大于左边基准,那么往前一个
            while (left<right&&arr[right]>temp){
                right--;
            }
            if (left<right) {
                //如果右边的值小于左边,那么交换位置
                //把在right处的值给到基准数处,然后left执行加一操作
                arr[left++] = arr[right];
            }
            //如果左边的值小于基准
            while(left<right && arr[left]<=temp) {
                left++;
            }
            //跳出上一个循环说明当前的arr[left]的值大于基准数,需要将该值填充到右边空出的位置,然后当前位置空出。
            if(left<right) {
                arr[right--]=arr[left];
            }
        }
        //将基准数填入最终的位置,这样在基准数左边都比基准数小,右边都比基准数大
        arr[left] = temp;
        return left;
    }


}

先把这个数组进行快排,得到排序后的数组,取nums[numSize-k]位置的元素就是所要求的最大值。

image.png

不过这种方式比较耗时,并不是最优解答。

官方答案验证

class Solution {
    Random random = new Random();

    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, nums.length - k);
    }

    public int quickSelect(int[] a, int l, int r, int index) {
        int q = randomPartition(a, l, r);
        if (q == index) {
            return a[q];
        } else {
            return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
        }
    }

    public int randomPartition(int[] a, int l, int r) {
        int i = random.nextInt(r - l + 1) + l;
        swap(a, i, r);
        return partition(a, l, r);
    }

    public int partition(int[] a, int l, int r) {
        int x = a[r], i = l - 1;
        for (int j = l; j < r; ++j) {
            if (a[j] <= x) {
                swap(a, ++i, j);
            }
        }
        swap(a, i + 1, r);
        return i + 1;
    }

    public void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

image.png

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

总结

这道题稍微有点难度,快速排序还是很需要逻辑性的。