问题:给定一个数组,对于某个区间,找出其中第 kkk 大的元素。

针对这个问题,有一个经典的算法被称为 快速选择算法,它可以在 O(n)\mathcal{O}(n)O(n) 的时间复杂度内找出区间内第 kkk 大元素。

还记得快速排序中的一个步骤——划分区间么?经过一个 O(n)\mathcal{O}(n)O(n) 复杂度的区间划分过程,可以将数组分为两部分,第一部分的元素值都小于等于 pivotpivotpivot,第二部分的元素值都大于等于 pivotpivotpivot

接下来,如果 pivotpivotpivot 新的下标 mid=k−1mid=k-1mid=k1,则直接返回 arrpivotarr_{pivot}arrpivot;否则,如果 k−1<midk-1<midk1<mid,则去左半部分区间继续查找第 kkk 大元素;否则,去右半部分区间继续查找第 k−mid−1k-mid-1kmid1 大元素。

ok

// 选择排序的partition
int partition(vector<int> &nums, int left, int right) {
   
    int p = nums[left], l = left, r = right;
    while(l < r) {
   
        while(l < r && nums[r] >= p) --r;
        nums[l] = nums[r];
        while(l < r && nums[l] <= p) ++l;
        nums[r] = nums[l];
    }
    nums[l] = p;
    return l;
}

int quick_select(vector<int> &nums, int left, int right, int k) {
   
    if(left == right) return nums[left];
    int p = partition(nums, left, right);
    if(p - left == k - 1) return nums[p];
    if(k - 1 < p - left) {
   
        return quick_select(nums, left, p - 1, k);
    }else {
   
        return quick_select(nums, p + 1, right, k - 1 - ( p - left ));
    }
}

需要注意的一点是 k - 1 与 p - left 是等价单位
所以进行一些比较以及运算的时候,需要注意一下。