找到无序数组中第K大的数,这道题蜜汁经典,腾讯和头条的面试中都被问到了,我一般用的都是大小为K的小顶堆和快排扫一半这两种思路做这道题,直到前几天面试头条的时候,终面面试官问,有没有 O(1) 空间【严格的 O(1) 空间,即不能用递归】且不改变原数组的情况下,找第K大的数。当场没有想起来,下来之后突然就想到了。所以特此来总结一下。
找数组第K大的数 ,Leetcode上有一道原题:
215. Kth Largest Element in an Array
常规的两种方法——小顶堆和快排分治
快排扫一半
就是快排每次扫完之后,看轴值得位置在哪里,如果大于k则扫前一半,小于k则扫后一半,如果等于k则说明找到了【PS,这里的k是指从0开始数的第K大的数】
这种方法的时间复杂度,平均意义上是 O(n)
int findKsort(vector<int>& nums,int k,int left,int right){
if(left==right) //其实这一步也可以不加的,因为下面有判断出如果相等的话,就直接返回了
return nums[left];
int i = left;
int j = right;
int q = nums[j];
while(i<j){
while(i<j && nums[i]>q) i++;
nums[j] = nums[i];
while(i<j && nums[j]<=q) j--;
nums[i] = nums[j];
}
nums[i] = q;
if(i == k)
return nums[i];
if(i>k)
return findKsort(nums,k,left,i-1); //顺便要注意这个地方是i-1和i+1,不能是i否则会死循环。
else
return findKsort(nums,k,i+1,right);
}
int findKthLargest(vector<int>& nums, int k) {
return findKsort(nums,k-1,0,nums.size()-1);
}
K大小的小顶堆
这种方法就是,先把数组中前K个数建立一个小顶堆,然后再依次往后扫,如果小于堆顶元素则继续往后扫,如果大于堆顶元素,则把堆顶元素pop出,然后再push进这个元素。
这种算法的复杂度是 O(nlogK)
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int,vector<int>,greater<int>> q;
for(int i=0;i<k;i++)
q.push(nums[i]);
for(int i = k;i<nums.size();i++){
if(nums[i]>q.top()){
q.pop();
q.push(nums[i]);
}
}
return q.top();
}
O(1)空间,不改变原数组的做法
这其实也是一个二分法啦,只不过之前的二分法都是从数组索引的角度去二分,而这次等于是二分一个数是否是满足第K大数的条件。 【即相当二分法求一个单调函数值 f(x)−k=0 ,依次去二分x看这个函数什么时候相等。】
这种方法的复杂度是需要讨论的,如果数组中全都是int类型的整数,那么时间复杂度是 O(n)
因为本质上查找一遍数组需要 O(n) ,而至多需要查找 log(Max−Min)≤32 所以至多查找32遍,所以复杂度还是 O(n) 的
PS,这种方法其实还是只是适用于整数,因为如果是浮点数的话,其复杂度是不可估计的。
int findKthLargest(vector<int>& nums, int k) {
int left = INT_MAX;
int right = INT_MIN;
for(auto x:nums){
left = min(x,left);
right = max(x,right);
}
while(left<=right){
int mid = left + (right-left)/2;
int count1 = 0;
int count2 = 0;
for(auto x:nums){
if(x>=mid)
count1++;
if(x>mid)
count2++;
}
if(count1 >=k && count2 <k)
return mid;
if(count1 <k)
right = mid-1;
else
left = mid+1;
}
return -1;
}