leetcode 力扣 215 数组中的第K个最大元素

122 阅读2分钟

魔改快速排序

首先来看两种写法

先来看官方的

class Solution {
    private final static Random random = new Random(System.currentTimeMillis());

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

    private int partition(int[] nums, int left, int right, int targe) {
        if (left == right) {
            return nums[targe];
        }

        int randomIndex = left + random.nextInt(right - left + 1);
        swap(nums, left, randomIndex);

        int pivot = nums[left], i = left - 1, j = right + 1;

        while (i < j) {
            do {
                i++;
            } while (nums[i] < pivot);
            do {
                j--;
            } while (nums[j] > pivot);

            if (i < j) {
                swap(nums, i, j);
            }
        }

        if (targe <= j) {
            return partition(nums, left, j, targe);
        } else {
            return partition(nums, j + 1, right, targe);
        }
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

接着是熟悉的快排传统写法

class Solution {
    private final static Random random = new Random(System.currentTimeMillis());

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

    private int partition(int[] nums, int left, int right, int targe) {
        if (left == right) {
            return nums[targe];
        }

        int randomIndex = left + random.nextInt(right - left + 1);
        swap(nums, left, randomIndex);

        int pivot = nums[left], i = left, j = right;

        while (i < j) {
            while (i < j && nums[j] >= pivot)
                --j;
            nums[i] = nums[j];
            while (i < j && nums[i] <= pivot)
                ++i;
            nums[j] = nums[i];
        }
        nums[i] = pivot;

        if (targe <= j) {
            return partition(nums, left, j, targe);
        } else {
            return partition(nums, j + 1, right, targe);
        }
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

其中,官方的快排只需要4ms(使用了随机pivot),而传统的写法需要上千毫秒,而两者仅仅是while(i < j)中的写法不同而已,真是神奇

那么就以官方写法为准
算法思路
在快速排序中,每一轮遍历都会确定一个pivot的最终位置。如下图所示,第一轮确定了3的最终位置

lc215_1.jpeg

lc215_2.jpeg

lc215_3.jpeg

如果确定的pivot的位置,刚好是我们需要找的K呢?也就是len - k,那么是不是就找到了答案。要注意的是,整个算法执行完,数组可能还是未排序完全的,但我们只需要关注pivot的位置是否等于len - k,那么怎么确定什么时候找到len - k呢?

我们可以使用两个指针leftright来作为每次递归的边界,再用两个指针ij作为工作指针,i = left - 1, j = right + 1,设len - ktarget
如果一轮遍历下来,jtarget的左边,那么下一轮递归将left设为j + 1right不变;
如果一轮遍历下来,jtarget的右边,或者target == j,那么下一轮递归将right设为jleft不变。
这样做都是为了使leftrighttarget逼近,俗称夹逼法。。。
left == right,说明找到了target

注意边界条件nums[i] < pivotnums[j] > pivot中都不是<=>=,是为了使pivot可以得到交换