问题:给定一个数组,对于某个区间,找出其中第 kkk 大的元素。
针对这个问题,有一个经典的算法被称为 快速选择算法,它可以在 O(n)\mathcal{O}(n)O(n) 的时间复杂度内找出区间内第 kkk 大元素。
还记得快速排序中的一个步骤——划分区间么?经过一个 O(n)\mathcal{O}(n)O(n) 复杂度的区间划分过程,可以将数组分为两部分,第一部分的元素值都小于等于 pivotpivotpivot,第二部分的元素值都大于等于 pivotpivotpivot。
接下来,如果 pivotpivotpivot 新的下标 mid=k−1mid=k-1mid=k−1,则直接返回 arrpivotarr_{pivot}arrpivot;否则,如果 k−1<midk-1<midk−1<mid,则去左半部分区间继续查找第 kkk 大元素;否则,去右半部分区间继续查找第 k−mid−1k-mid-1k−mid−1 大元素。
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 是等价单位
所以进行一些比较以及运算的时候,需要注意一下。