「数组」随机快速选择 / LeetCode LCR 076(C++)

前置知识

在本篇文章之前,你应该先掌握快速排序的基本技巧,详见:「数组」快速排序 / 随机值优化|小区间插入优化(C++)

概述

LeetCode LCR 076是这么一道题:

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

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

示例 :

复制代码
输入: [3,2,1,5,6,4] 和 k = 2输出: 5

这样的题目可以直接通过快速选择进行完全排序,但时间复杂度是O(nlogn)。如果我们要求必须在O(n)时间内得到结果呢?随机数优化的快速排序变体:随机快速选择能完成这个工作。

思路

在快速选择中,我们不得不进行全部的递归与回溯过程来实现数组的完全排序。

但是在只要求某个元素位置正确时,我们注意到:

cpp 复制代码
void quick_sort(int arr[], int l,int r) {
	if (r-l<=1)return ;
	int pos = partition(arr, l, r);
	quick_sort(arr, l, pos);
	quick_sort(arr, pos + 1, r);
}

两个子区间排序的其中一个是不必要的,并且如果已经安放了正确的元素位置,以后的所有递归都是不必要的。

算法过程

那么快速选择过程就可以进行对应的优化。

这就意味着:

①我们只需要判断parition分区函数返回的pos与期望位置之间的关系,并转发给对应的子排序

②当got_ans==true时,我们可以直接返回来实现剪枝。

cpp 复制代码
void quick_select(vector<int>& nums,int l,int r,int& k,const int& len,bool& got_ans){
        if(r-l<1||got_ans)return ;
        int pos=partition(nums,l,r);
        if(pos==len-k){got_ans=true;return;}
        if(pos>len-k)quick_select(nums,l,pos,k,len,got_ans);
        if(pos<len-k)quick_select(nums,pos+1,r,k,len,got_ans);
    }

partition函数仍然保持原状:

cpp 复制代码
int partition(vector<int>& nums,int l,int r){
        int pivot=l+mt()%(r-l);
        swap(nums[pivot],nums[r-1]);
        int i,j;
        for(i=l,j=l;j<r;j++)if(nums[j]<=nums[r-1])swap(nums[i++],nums[j]);
        return i-1;
    }

由于此算法是随机快速排序的特化体,故为随机快速选择。

复杂度

时间复杂度:O(n)

空间复杂度:O(logn)

复杂度分析

时间分析:

在理想情况下,每次都转发给了排序范围折半的子函数。

总用时为T(n),两个T(n/2)为下一级的总时间,n为本次分区所用时间,

T(n)=T(n/2)+n

=T(n/4)+n/2+n

...

=T(1)+n(1-1/2^n)/(1-1/2)

=1+2n+n/2^(n-1)

省去小量,得到O(n)


空间分析:

与快速排序相同,每一级子函数都使用了常量空间,因此空间复杂度是logn级别的。

Code

cpp 复制代码
class Solution {
private:
    mt19937 mt;
public:
    int partition(vector<int>& nums,int l,int r){
        int pivot=l+mt()%(r-l);
        swap(nums[pivot],nums[r-1]);
        int i,j;
        for(i=l,j=l;j<r;j++)if(nums[j]<=nums[r-1])swap(nums[i++],nums[j]);
        return i-1;
    }
    void quick_select(vector<int>& nums,int l,int r,int& k,const int& len,bool& got_ans){
        if(r-l<1||got_ans)return ;
        int pos=partition(nums,l,r);
        if(pos==len-k){got_ans=true;return;}
        if(pos>len-k)quick_select(nums,l,pos,k,len,got_ans);
        if(pos<len-k)quick_select(nums,pos+1,r,k,len,got_ans);
    }
    int findKthLargest(vector<int>& nums, int k) {
        bool got_ans=false;
        const int len=nums.size();
        quick_select(nums,0,len,k,len,got_ans);
        return nums[len-k];
    }
};
相关推荐
多打代码5 小时前
2025.09.05 用队列实现栈 & 有效的括号 & 删除字符串中的所有相邻重复项
python·算法
mit6.8246 小时前
并查集|栈
c++
中国胖子风清扬6 小时前
Rust 序列化技术全解析:从基础到实战
开发语言·c++·spring boot·vscode·后端·中间件·rust
j_xxx404_6 小时前
数据结构:栈和队列力扣算法题
c语言·数据结构·算法·leetcode·链表
南莺莺6 小时前
假设一个算术表达式中包含圆括号、方括号和花括号3种类型的括号,编写一个算法来判别,表达式中的括号是否配对,以字符“\0“作为算术表达式的结束符
c语言·数据结构·算法·
Lris-KK7 小时前
【Leetcode】高频SQL基础题--180.连续出现的数字
sql·leetcode
THMAIL7 小时前
深度学习从入门到精通 - 神经网络核心原理:从生物神经元到数学模型蜕变
人工智能·python·深度学习·神经网络·算法·机器学习·逻辑回归
珍珠是蚌的眼泪7 小时前
LeetCode_位运算
leetcode·位运算·异或·韩明距离·数字的补数
野犬寒鸦7 小时前
力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
java·数据结构·后端·算法·leetcode
墨染点香7 小时前
LeetCode 刷题【61. 旋转链表】
算法·leetcode·职场和发展