【算法】O(1)空间,不改变原数组的情况下找第K大的数

找到无序数组中第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(MaxMin)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;
}
点赞