1、题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
说明:你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
2、示例
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
3、题解
解法一:
基本思想:快速选择算法,只要n-k的左边是小于nums[n-k]的值,n-k的右边是大于nums[n-k]的值,那么n-k就是我们要找的Top k,问题转化为如何切割左右数组,并找到 Top k 对应的 pivot。
解法二:
基本思想:构造前k个最大元素小顶堆,取堆顶,堆顶元素最小
解法三:
基本思想:手动实现堆排序,构造大顶堆,因为大顶堆最后得到的是从小到大的顺序,构造大顶堆后,从堆尾先得到最大元素依次得到第k大元素即可。堆排序(Heap Sort)的时间复杂度是O(nlogn),最坏情况下也是如此。而快速排序(Quick Sort),若初始记录序列有序,快速排序将退化为起泡排序(Bubble Sort), 时间复杂度是O(n^2)。
这是堆排序比快速排序的优点
- 堆排序第一步建立大顶堆,从下面节点往上面节点建立,就是保证当前节点i值大于两个孩子节点2*i+1和2*i+1值
- 对于堆节点nums[start]进行调整,若小于两个孩子节点nums[i]nums[i+1]中最大的则调整,否则break
- nums[start]大于两个孩子节点中最小的,调整
- 看调整后start的孩子节点i是否大于i的孩子节点,因此更新start和i
- 堆排序第二步依次交换堆顶和堆底, 这样堆底就是排序后的最大值,该节点也去除在堆中,大顶堆被破坏重新建立堆,同时堆的范围改变,节点在逐渐减少
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//基本思想:快速选择算法,只要n-k的左边是小于nums[n-k]的值,n-k的右边是大于nums[n-k]的值
//那么n-k就是我们要找的Top k,问题转化为如何切割左右数组,并找到 Top k 对应的 pivot
int target=nums.size()-k;
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=Partition(nums,left,right);
if(mid==target)
return nums[mid];
if(mid<target)
left=mid+1;
else
right=mid-1;
}
return nums[left];
}
int Partition(vector<int>& nums,int left,int right)
{
int pivot=nums[left];
while(left<right)
{
while(left<right&&pivot<=nums[right])
right--;
swap(nums[left],nums[right]);
while(left<right&&nums[left]<=pivot)
left++;
swap(nums[left],nums[right]);
}
return left;
}
};
class Solution1 {
public:
int findKthLargest(vector<int>& nums, int k) {
//基本思想:构造前k个最大元素小顶堆,取堆顶,堆顶元素最小
priority_queue<int, vector<int>, greater<int>> q;
for (auto num : nums)
{
if(q.size() == k && num < q.top())
continue;
q.push(num);
if (q.size() > k)
q.pop();
}
return q.top();
}
};
class Solution2 {
public:
int findKthLargest(vector<int>& nums, int k) {
//基本思想:手动实现堆排序,构造大顶堆,因为大顶堆最后得到的是从小到大的顺序
//构造大顶堆后,从堆尾先得到最大元素依次得到第k大元素即可
HeapSort(nums, k);
return nums[nums.size() - k];
}
//调整大顶堆,保持节点nums[start]大于孩子节点,调整范围在[start+1,end]之间
void HeapAdjust(vector<int>& nums, int start, int end)
{
int temp = nums[start]; //保存nums[start]值
int i = 2 * start + 1; //start的一个孩子节点nums[2*start+1]另一个是nums[2*start+2]
while (i <= end)
{
//对于堆节点nums[start]进行调整,若小于两个孩子节点nums[i]nums[i+1]中最大的则调整,否则break
if (i + 1 <= end && nums[i] < nums[i + 1])
i = i + 1;
if (temp > nums[i])
break;
nums[start] = nums[i]; //nums[start]大于两个孩子节点中最小的,调整
start = i; //看调整后start的孩子节点i是否大于i的孩子节点,因此更新start和i
i = 2 * start + 1;
}
nums[start] = temp;
}
void HeapSort(vector<int>& nums,int k)
{
//堆排序第一步建立大顶堆,从下面节点往上面节点建立,就是保证当前节点i值大于两个孩子节点2*i+1和2*i+1值
for (int i = nums.size() / 2 - 1; i >= 0; i--)
{
HeapAdjust(nums, i, nums.size() - 1);
}
//堆排序第二步依次交换堆顶和堆底, 这样堆底就是排序后的最大值,该节点也去除堆中,大顶堆被破坏重新建立堆
int i = nums.size() - 1;
while(k--)
{
swap(nums[0], nums[i]);
HeapAdjust(nums, 0, i - 1);
i--;
}
}
};
int main()
{
Solution solute;
vector<int> nums = { 3,2,3,1,2,4,5,5,6 };
int k = 4;
cout << solute.findKthLargest(nums,k) << endl;
return 0;
}