这个问题比较经典,有很多解法,这里介绍几个经典解法。
基于Partition函数
基于快速排序中Partition函数的思想,每次在数组中选择一个数m,把小于m的数放到左边,大于m的数放到右边,看m的位置递归左边或者右边,最后找到第K大的数。
分析此算法的时间复杂度
首先快速排序的平均时间复杂度是O(N*logN),最差情况下(数组有序时)时间复杂度是O(N*N)。
此算法最差情况下也是数组有序时,时间复杂度为O(N*N)。但平均时间复杂度为O(N),因为假设每次丢掉一半的数组,则一共遍历了(1 + 1/2 + 1/4 + …) < 2,所以为O(2N),即为O(N)时间复杂度。
代码:
class Solution{
public:
int findKthLargest(vector<int>& nums, int k){
if(k > nums.size())
return 0;
return findKthLargest(nums, 0, nums.size() - 1, k);
}
int findKthLargest(vector<int>& nums, int begin, int end, int k){
if(nums.size() <= 0 || k < 1 || begin > end)
return 0;
int flag = nums[begin], i = begin, j = end;
while(i < j){
while(nums[j] >= flag && i < j)
j--;
nums[i] = nums[j];
while(nums[i] < flag && i < j)
i++;
nums[j] = nums[i];
}
nums[i] = flag;
if(end - i + 1 == k)
return nums[i];
else if(end - i + 1 < k) // 留左边
return findKthLargest(nums, begin, i - 1, k - end + i - 1);
else // 留右边
return findKthLargest(nums, i + 1, end, k);
}
};
基于堆排序(内存足够存放所有数据)(也可用于Top K)
堆排序的初始建堆操作时间复杂度为O(N)(可证,通过级数),之后每加入新元素或者取走堆顶元素的时间复杂度为O(logN),所以堆排序的时间复杂度为O (N*logN) 。
因为每次取堆顶的元素都是最大/最小的,这里建大顶堆的话,取到第K大的元素时间复杂度为O(N+K*logN),当K不大时,也是比较快的。
class Solution{
public:
void AdjustHeap(vector<int>& nums, int i, int n){
// nums[0] 是堆顶,堆中共有n个元素,现在调整 i
int root = i, j = root * 2 + 1;
while(j < n){
if(j + 1 < n && nums[j + 1] > nums[j]) // 对比左右子节点的大小
j++;
if(nums[root] < nums[j]){
swap(nums[root], nums[j]);
root = j;
j = root * 2 + 1;
}
else
break;
}
}
int findKthLargest(vector<int>& nums, int k){
if(k > nums.size())
return 0;
// 初始化大顶堆
for(int i = nums.size() / 2 - 1; i >= 0; --i) // 从 n/2-1 开始
AdjustHeap(nums, i, nums.size());
// 对k-1个元素出堆
for(int i = 0; i < k - 1; ++i){
nums[0] = nums[nums.size() - 1 - i]; // 把堆中最后一个元素移到堆顶
AdjustHeap(nums, 0, nums.size() - i);
}
return nums[0];
}
};
基于堆排序(内存不足够存放数据)(不修改原数组)(也可用于Top K)
当数据量特别大,以至于内存中装不下,只能装入硬盘的时候。或者要求不修改原数组时。
这时可以先取K个元素,建一个K个元素的小顶堆,之后每取一个元素就与堆顶的元素对比,小的话就扔掉,大的话就把堆顶元素出堆,把新元素入堆,保证目前遍历的所有元素中前K大的元素都在堆中。遍历结束后,堆顶的元素就是第K大的元素。
这个算法的时间复杂度为O(N*logK)。