数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
❤❤❤基于快排的快速选择(递归(选择判断->pivot获取->partition)和双指针)
思路
快速排序为典型的分治算法,对a[l...r]排序的过程可以分解如下:
- 分解:取任意下标
pivot,并将数组划分成两个子数组,a[l...pivot-1](元素均<=a[pivot])和a[pivot+1...r](元素均>a[pivot])。 - 解决:对子数组
a[l...pivot-1]和a[pivot+1...r]重复执行(1) - 合并:将子数组合并,若为原址排序,不需要这一步。
算法
由于每次分解都可以得到从小大到排序的第pivot+1个数,且第k个最大元素实为从小到大排序第n-k个数,故如下:
- 若
pivot==n-k时,则返回nums[pivot]; - 若
pivot<n-k时,递归a[pivot+1...r] - 若
pivot>n-k时,递归a[l...pivot+1]实现-递归
```cpp int quickSelect(vector& nums,int l,int r,const int idx){ int curIdx= randomQS(nums,l,r); if(curIdx==idx){
} else{return nums[curIdx];
} } int randomQS(vectorreturn curIdx<idx?quickSelect(nums,curIdx+1,r,idx):quickSelect(nums,l,curIdx-1,idx);
& nums,int l,int r){ //基于随机值来选取pivot int pivot=rand()%(r-l+1)+l; swap(nums[pivot],nums[r]); return patition(nums,l,r); } int __patition(vector& nums,int l,int r){ //根据x值来划分左右区间,满足左边<=x<右边 int x=nums[r],j=l-1; for(int i=l;i<r;i++){
} swap(nums[++j],nums[r]); return j; } int findKthLargest(vectorif(nums[i]<=x){//j保留pivot左侧的位置swap(nums[i],nums[++j]);}
& nums, int k) { srand(time(0));//保证每次随机值不相同 return __quickSelect(nums,0,nums.size()-1,nums.size()-k); }
//寻找中位数
int __median_of_three(const vector
<a name="1oOD6"></a>### 实现-双指针```cppint findKthLargest(vector<int>& nums, int k){srand(time(0));k=num.size()-k;int l=0,r=nums.size()-1;while(l<r){int rlt=quickSort(nums,l,r);if(rlt==k) break;else if(rlt<l) l=rlt+1;else r=rlt-1;}return nums[k];}int quickSort(vector<int>& a,int l,int r){int __cnt=rand()%(r-l+1)+l;swap(nums[l],nums[__cnt]);int pivot=l;while(l<r){while(nums[r]>=nums[pivot] && r>l) r--;while(nums[l]<=nums[pivot] && r>l) l++;swap(nums[l],nums[r]);}swap(nums[pivot],nums[l]);return l;}
复杂度分析
- 时间复杂度:
- 空间复杂度:
我们知道快速排序的性能和「划分」出的子数组的长度密切相关。直观地理解如果每次规模为 n 的问题我们都划分成 1 和 n - 1,每次递归的时候又向 n - 1 的集合中递归,这种情况是最坏的,时间代价是 。我们可以引入随机化来加速这个过程,它的时间代价的期望是
,证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」
❤❤基于堆排序的选择方法(建堆-调整堆)
建堆的方法核心在:递归交换,堆实质上是【父-左右孩子】约束关系形成的数据结构 在第一次建堆时,需要从下往上递归的进行交换,以后每次添加或删除,都仅需要从上到下进行一次递归交换 当建堆完成时:
- 对于pop,将根元素=最后一个元素,并从上到下递归交换(数组长度-1后交换)
- 对于push,数组push_back后,从上到下递归交换(数组长度+1后交换)
思路

注意,每次交换后,都需要对下一层的子堆递归调整,因为交换后破坏了已调整的子堆结构。
假设将数组[0,1,2,3,4,...,n-1]看作一个完全二叉树,则对应父节点i,有
- 左孩子:
2*i+1 - 右孩子:
2*i+2
大根堆的调整方法(递归交换):
从根节点开始
- 对当前【父节点-左/右孩子】实施交换,并在下一层交换处(若为大根堆,则为左右子树大者)递归实施交换。
- 递归到最后一个非叶子节点时结束。
大根堆的建树方法:
- 交换:
maxHeapify(arr,i,last)
递归的实现【父节点-左/右孩子】的交换
- 弹出:
- 将root以大根堆的最后一个元素替换。
- 实施一次
maxHeapify(arr,0,last)实现
```cpp / 从根节点开始递归调整 / void __maxHeapify(vector& nums,int i,int heapSize){ int l=2i+1,r=2i+2,bigger=i; if(l nums[bigger]){
} if(rbigger=l;
nums[bigger]){
} if(i!=bigger){bigger=r;
} }/* 若当前节点调整,则递归调整下一层 */swap(nums[i],nums[bigger]);__maxHeapify(nums,bigger,heapSize);
/ 建树-从最后一个非根节点开始实施递归交换 /
/ 最后一个非根节点 : headSize/2 /
void buildHeap(vector
int findKthLargest(vector
复杂度分析
- 时间复杂度:
- 空间复杂度:
题目标题难度划分
| 星级 | 题目难度 |
|---|---|
| ☆ | 简单 |
| ☆☆ | 中等 |
| ☆☆☆ | 困难 |
算法掌握难度程度划分
| 星级 | 掌握难度 |
|---|---|
| 无 | 普通 |
| ❤ | 经典,易掌握 |
| ❤❤ | 经典,略难掌握 |
| ❤❤❤ | 困难 |
