数组中的第k个最大元素:快速选择算法

293 阅读5分钟

思路

  • 首先想到的是快速排序(快速排序思路见第三条),将给定的输入数组nums按照从小到大的顺序排序,然后直接返回数组的第 nums.length - k + 1个元素(也就是返回nums[nums.length - k + 1])

  • 但是我们知道在快速排序中我们一般使用两个指针去遍历这个数组,遍历一次数组,就确定一个x值的位置,然后确保x左边的元素都小于x,x右边的元素都大于x,然后通过分治的思想不断划分数组,也就是将x值的左右两边进行划分,分别再去这两个划分的子数组的x1,x2,确保x1左边的元素都小于x1,x1右边的元素都大于x1,x2亦是如此。这样平均时间复杂度是 O(nlogn),但其实我们可以做的更快,因为在最坏的情况下,比如数组按照从大到小的顺序排列好了,我们再去使用快排,将它从小到大进行排列,这时候就属于最坏情况,时间复杂度有n^2,于是,我们可以根据题目要求适当的优化一下快速排序算法,我们只需要得到下标为 nums.length - k 的元素,由于快速排序每次递归我们都可以确定一个x的位置,而x的值也在每次递归的前期就确定好了的,所以在递归期间,两个前后指针相等将要结束递归前,我们做个判断,判断这两个指针代表的下标值是否等于 nums.length - k ,含义是,这两个指针代表的值是否是数组中的第k个最大元素,如果等于 nums.length - k,那么就直接返回nums[ nums.length - k],如果指针小于 nums.length - k ,说明我们要找的第k的最大元素在本次递归时确定的Xn的右边,那Xn左边的的数列就不需要再递归的遍历的,左边肯定没有我们要找的值,如果大于 nums.length - k, 说明我们要找的第k的最大元素在本次递归时确定的Xn的左边,那右边就不需要再找了,大大减少遍历和递归的成本。

  • 快速排序

    • 定义两个指针,初始设置为0和数组的末尾下标,设置base值为startIndex所指向的值

    • 首先从后往前,如果nums[endIndex] > base,那么久endIndex--,知道从后往前找到第一个小于base的,将endIndex所指向的这个小于base值和当前startIndex指向的值交换(目的是确保最终startIndex和endIndex所指向的共同的x的左边的数都小于x,右边的数都大于x),交换完之后,startIndex++,直到strartIndex找到第一个大于base的值,这之后将startIndex所指向的这个大于base值和当前endIndex指向的值交换,交换完之后,再将endIndex--,也就将endIndex向前移动

      • 从代码上看,endIndex存在一直往后移动的操作,直到某个值小于base,所以这个移动操作需要一个循环A来做,startIndex存在一直往前移动的操作,直到某个值大于base,所以这个移动操作需要一个循环B,然后endexIndex和startIndex的移动存在交替,startIndex移动到某点交换元素后,轮到endIndex移动,endIndex移动完交换好元素后轮到startIndex移动,这也可以用一个循环来做:只要startIndex < endIndex,就一直在一个大循环里面,不断做A,B两个循环,见下面伪代码
    • 一轮递归结束之后,我们找到了最开始的base值在最终的有序数组中的位置,也就是说,其实这个确认的x值就是这个base,我们确认的是这个x也就是base值的位置下标,确认好后,对这个值的左右两边的元素进行递归,直到这个递归的子数组为单个元素为止。这里需要注意,我们递归子数组并不是创建一个又一个子数组,我们只需要传递给定的输入的原始数组,添加我们希望访问的属于原数组的子数组下标,也就是通过startIndex和endIndex来访问原数组的子数组即可。

 while (startIndex < endIndex) {
                    while (arr[endIndex] >= base && startIndex < endIndex) {
                        endIndex--;
                    }

                    swap(arr, startIndex, endIndex)

                    while (arr[startIndex] <= base && startIndex < endIndex) {
                        startIndex++;
                    }
                    swap(arr, startIndex, endIndex)

                }
 if (startIndex === (nums.length - k)) {
                    return arr[startIndex]
                } else if (startIndex < (nums.length - k)) {
                    return partion(arr, startIndex + 1, endIndex2);
                } else if (startIndex > (nums.length - k)) {
                    return partion(arr, startIndex2, startIndex - 1);
                }

题目

给定整数数组 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

代码

  var findKthLargest = function (nums, k) {
            let startIndex = 0;
            let endIndex = nums.length - 1;
            let res = null;

            const partion = (arr, startIndex, endIndex) => {
                if (startIndex > endIndex) return null;


                let base = arr[startIndex];
                let endIndex2 = endIndex;
                let startIndex2 = startIndex;

                const swap = (arr, i, j) => {
                    let temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
                while (startIndex < endIndex) {
                    while (arr[endIndex] >= base && startIndex < endIndex) {
                        endIndex--;
                    }

                    swap(arr, startIndex, endIndex)

                    while (arr[startIndex] <= base && startIndex < endIndex) {
                        startIndex++;
                    }
                    swap(arr, startIndex, endIndex)

                }
                if (startIndex === (nums.length - k)) {
                    return arr[startIndex]
                } else if (startIndex < (nums.length - k)) {
                    return partion(arr, startIndex + 1, endIndex2);
                } else if (startIndex > (nums.length - k)) {
                    return partion(arr, startIndex2, startIndex - 1);
                }

            }
            return el = partion(nums, startIndex, endIndex)
        };