数组中第K大元素
在面试的时候遇到过这个问题,后来在leetcode上也遇到了这个问题,于是记录下来以便以后快速回忆。
题目有多种思路,全排序是比较直观的想法,然而最低的时间复杂度为O(nlgn),并且不符合该题目的初衷。
题目更多想问的是如何在不进行全排序的条件下找到数组中第K大的元素,个人认为比较被面试官中意的解答有两个(理应也有其他的..),如下:
构建最大堆
简单分析时间复杂度: 构建堆的时间O(n),k次取最大元素要k(lgn),最终的时间复杂度为O(n+klgn).
显然是比全排序要好一些,但是当时面试的时候我回答这个思路,面试官认为并不是一个好的思路,因为面试官认为构建最大堆更适合题目:求数组中前K大的元素(维护一个大小为K的最大堆).
然而我还是认为这是一个可以的思路…..恩,代码还是贴一下,以后就省事了..
int heap_size;
int parent(int idx) {
return (idx - 1) >> 1; //求父节点
}
int left(int idx) {
return (idx << 1) + 1;//左子树
}
int right(int idx) {
return (idx << 1) + 2;//右子树
}
void max_heapify(vector<int>& nums, int idx) {
int largest = idx;
int l = left(idx), r = right(idx);
if (l < heap_size && nums[l] > nums[largest]) largest = l;
if (r < heap_size && nums[r] > nums[largest]) largest = r;
if (largest != idx) {
swap(nums[idx], nums[largest]);
max_heapify(nums, largest);
}
}
//构建最大堆,从非叶子节点开始操作,可证明时间复杂度是O(n)
void build_max_heap(vector<int>& nums) {
heap_size = nums.size();
for (int i = (heap_size >> 1) - 1; i >= 0; i--)
max_heapify(nums, i);
}
int findKthLargest(vector<int>& nums, int k) {
build_max_heap(nums);
for (int i = 0; i < k; i++) {
swap(nums[0], nums[heap_size - 1]);
heap_size--;
max_heapify(nums, 0);
}//不断交换末尾元素和最大元素,之后重新max_heapify
return nums[heap_size];
}
快速排序思路
该思想类似快速排序:
以某一个元素为pivot元素,将元素分为两个集合,一个集合元素比pivot小,另一个比pivot大。
若比pivot大的元素数目正好为k-1,那么pivot就是我们要找到元素;若比pivot大的元素为m(小于k), 那么就在比pivot小的集合里面找第(k-m)大的元素; 若是比pivot大的元素为m(大于k),那就继续在该集合里面找第k大的元素。
重复上面步骤,直到找到第k大的元素
代码如下:
int partition(vector<int>& nums, int i, int j)
{//类似快速排序的分组
if (i == j) return i;
int pivot = nums[i];
std::swap(nums[i], nums[j]);
int i0 = i;
for(int k = i; k < j; k ++)
{
if(nums[k] <= pivot)
{
std::swap(nums[k], nums[i0 ++]);
}
}
std::swap(nums[i0], nums[j]);
return i0;
}
int findKthEle(vector<int>& nums, int i, int j,int k)
{
int index = partition(nums,i,j);
int length = j-index+1;
if(length == k)
return nums[index];
else if(length > k)
return findKthEle(nums,index+1,j,k);
else if(length <k)
return findKthEle(nums,i,index-1,k-length);
}
int findKthLargest(vector<int>& nums, int k)
{
size_t len = nums.size();
return findKthEle(nums,0,len-1,k);
}
时间复杂度:
该算法的平均时间复杂度为O(N)(详细的推导过程看算法导论9.2节),最坏情况为N^2,即每次划分把数组变为为(n-1) 和1的两断。
同时算法导论上有关于O(n)的算法(9.3节),其思路是选择pivot元素时是通过选择多组元素中的中位数,根据这个元素来划分,能够得到最坏情况为线性时间的选择算法(具体推导我还真是吃不消)。
看了算法导论以及网上相关的博客,发现这是个经典的老题目了…然而我并没有研究的非常透彻…