Kth Largest Element in an Array(数组第k大的数)
【难度:Medium】
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4] and k = 2, return 5.
给定一乱序数组,在其中找到第k个大的数字,如
C = [3,2,1,5,6,4], k = 2, 返回5。
解题思路
经典的Google搜索排名算法,可用的解决方法有:
- 1)使用常规排序方法后找到数组中对应下标的值;
- 2)将数组内容存入一升序优先队列中,进行k-1次pop操作,那么队尾的元素就是第k大的数字;
- 3)使用数组内容构建一个最大堆/最小堆,通过每次pop出堆顶后继续维护堆的结构,直到满足一定的次数(最大堆k-1次,最小堆size-k次),堆顶的元素就是第k大的数字,实现的效果与优先队列相同;
- 4)利用快排的partition函数思想,选定一个数组内的值作为pivot,将小于pivot的数字放到pivot右边,大于等于pivot的数字放到pivot左边。接着判断两边数字的数量,如果左边的数量小于k个,说明第k大的数字存在于pivot及pivot右边的区域之内,对右半区执行partition函数;如果右边的数量小于k个,说明第k大的数字在pivot和pivot左边的区域之内,对左半区执行partition函数。直到左半区刚好有k-1个数,那么第k大的数就已经找到了。
//利用优先队列
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
/** priority_queue<int, vector<int>, less<int>> q; **/
priority_queue<int, vector<int>> q;
int len=nums.size();
for(int val:nums){
q.push(val);
}
while(q.size() > len-k+1){
q.pop();
}
return q.top();
}
};
解法一:使用最小堆
这道题既可以使用最小堆也可以使用最大堆来求解,先说使用最小堆的解法。最小堆中最顶端的元素是堆中最小的,我们构建一个大小为k的最小堆,那么最顶端的元素就是第k大的数。
使用数组中的前k个数构成一个最小堆,那么堆顶元素就是第k大的数,然后从第k+1个数开始遍历数组,如果遍历的数组元素小于堆顶元素,舍弃掉,如果遍历的数组元素大于堆顶元素,将堆顶元素出堆,然后将大于堆顶元素的数组中的元素插入堆中,再次形成堆。这样遍历完数组堆顶元素就是第k大的元素,这种解法是保证在遍历到数组的任何一个元素时堆顶元素都是到遍历到的元素为止的第k大的元素。
runtime:8ms
int findKthLargest(vector<int>& nums, int k) {
make_heap(nums.begin(),nums.begin()+k,greater<int>());
for(int i=k;i<nums.size();i++)
{
int top=nums.front();
if(nums[i]>top)
{
pop_heap(nums.begin(),nums.begin()+k,greater<int>());
nums[k-1]=nums[i];
push_heap(nums.begin(),nums.begin()+k,greater<int>());
}
}
return nums.front();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
解法二:使用最大堆
使用最大堆也可以求解,就是将整个数组中的元素构成一个最大堆,这时堆顶元素是最大的,连续将堆顶元素弹出k-1次后堆顶元素就是第k大的数了。由于stl中默认构建的是最大堆,所以这种解法可能会更直观一些。
runtime:8ms
int findKthLargest(vector<int>& nums, int k) {
make_heap(nums.begin(), nums.end());
for (int i=1; i<k; i++){
pop_heap(nums.begin(), nums.end());
nums.pop_back();
}
return nums[0];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
解法三:分治法
分治法没有想出来,但是在Discuss中看到了一篇很详细的解答。我将它翻译成了中文。
这种解法是由快速排序发展起来的。
快速排序中,每一次迭代,我们需要选取一个关键元素pivot,然后将数组分割成三个部分:
- 小于关键元素pivot的元素
- 等于关键元素pivot的元素
- 大于关键元素pivot的元素
现在,以[3,2,1,5,4,6]这个数组为例来分析。假定每次选取最左端的元素作为关键的元素pivot,这种情况下,是3,然后我们使用3作为pivot将数组分成上面指定的3个部分,最后结果是[1,2,3,5,4,6]。现在3是第3个元素并且我们知道它也是第3小的元素。
由于上面的分割是将比pivot小的元素放在了pivot的左边,所以pivot当pivot在第k-1位置是是第k小的元素。由于这道题目需要寻找第k大的元素,我们可以修改一下分割过程将比pivot大的元素放在k的左边。这样,分割完成后数组变成了[5,6,4,3,1,2],现在3是第4大的元素,如果我们需要寻找第2大的元素,我们知道它是在3左边,如果我们需要第5大的元素,我们知道它是在3右边。
现在简单写出算法的流程:
- 初始化left为0,right为nums.size()-1
- 分割数组,如果pivot在第k-1位,返回pivot
- 如果pivot在k-1右边,更新right为pivot的位置值
- 否则更新left为pivot的位置值。
- 重复2的步骤
runtime:8ms
int findKthLargest(vector<int>& nums, int k) {
int pivot=nums[0];
int left=0;
int right=nums.size()-1;
while(true)
{
int pos=partion(nums,left,right);
if(pos==k-1) return nums[pos];
if(pos<k-1) left=pos+1;
else right=pos-1;
}
}
int partion(vector<int> &nums,int begin,int end)
{
int left=begin+1;
int right=end;
while(left<=right)
{
if(nums[left]<nums[begin]&&nums[right]>nums[begin])
swap(nums[left],nums[right]);
if(nums[left]>=nums[begin]) left++;
if(nums[right]<=nums[begin]) right--;
}
swap(nums[begin],nums[right]);
return right;
}