215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

分析:或许很多人面试的时候被问到过10万个数里面取第k大的数算法怎么设计,这种属于海量数据问题,思路又要变化下,你回答的时候肯定不可能直接给出一个算法就结束了,主要是如何针对不同场景和限制如何给出最优解,这就很考验基本功了。这里面最核心的就是考虑时间和内存的限制了。
先不管海量数据的情景,回到这题,我们一般考虑哪些思路。

1 最直接的想法,先对整个数组进行排序,然后取第k大的数。

至于如何排序,那内容就多了。比如快排,堆排,偷懒的人可能直接调用STL里的sort函数就过了,比如可以选择快排,比竟快排平均花费时间最少。
时间复杂度O(nlogn)

2 利用快速选择的想法。

也就是快排的分治思想。取一个指针pivot

  • 如果pivot == k,则正好找到了第k小的元素
  • 如果pivot > k,则第k小的元素存在于pivot左边
  • 如果pivot < k,则第k小的元素存在于pivot右边

3 利用最小堆

维护一个容量为k的最小堆,在建完堆后将取堆内第k个数即可。
关于快速选择的算法可以参考下算法导论。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.rbegin(), nums.rend());
        return nums[k - 1];
    }
};
class Solution {
    public int findKthLargest(int[] nums, int k) {
        shuffle(nums);
        k = nums.length - k;
        int low = 0;
        int high = nums.length - 1;
        while(low < high) {
            int pivot = partition(nums, low, high);
            if(pivot < k) {
                low = pivot + 1;
            } else if(pivot > k) {
                high = pivot - 1;
            } else {
                break;
            }
        }
        return nums[k];
    }
    //参考算法第四版
    int partition(int[] a, int low, int high) {
        int i = low;
        int j = high + 1;
        while(true) {
            while(j < high && less(a[++i], a[low]));
            while(j > low && less(a[low], a[--j]));
            if(i >= j) {
                break;
            }
            exch(a, i, j);
        }
        exch(a, low, j);
                    return j;
    }
    void exch(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    boolean less(int v, int w) {
        return v < w;
    }
    void shuffle(int a[]) {
    //在partition前将数组顺序打乱,保证不出现最坏情况。

        Random random = new Random();
        for(int i = 1; i < a.length; i++) {
            int r = random.nextInt(i + 1);
            exch(a, i, r);
        }
    }
}

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
        for(int val : nums) {
            pq.offer(val);
            if(pq.size() > k) {
                pq.poll();
            }
        }
        return pq.peek();
    }
}
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
    // 使用递减greater<int>函数对象排序
        priority_queue<int,vector<int>,greater<int>> pq;
        for(int i = 0; i < nums.size(); i++) {
            pq.push(nums[i]);
            if(pq.size() > k) {
                pq.pop();
            }
        }
        //返回队顶元素
        return pq.top();
    }
};

如果情境改为10亿个数的topK问题,怎么想。

最好想的,还是先全部排序,然后查找。

最快的排序算法的时间复杂度一般为O(nlogn),如快速排序。
但是如果计算机内存不够大,这种方法很容易受内存限制。

主要解决方案:

  • 分治+Trie树

  • hash+小顶堆

局部淘汰法

用一个容器保存前1万个树,然后将剩余的所有数字一一和容器内最小的数字比较,如果后续元素比容器内的最小元素大就删掉容器内的最小元素,并将该元素插入容器,最后遍历玩这1亿个数,得到最终结果。

分治

将1亿个数据分成100份,每份100万个数据,找出每份数据中最大的1万个,最后在剩下的100 X 10000个数据。也就是100万个数据里面找前1万

哈希

通过hash法,把这1亿个数字去重复,这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间,

最小堆

先读入前10000个数来创建大小为10000的小顶堆,建堆的时间复杂度为O(mlogm),然后遍历后续数字,并与堆顶数字(最小)进行比较,如果比堆顶的数字大,替换堆顶元素调整堆为小顶堆。这个过程直到1亿个数全部遍历完。然后按照中序遍历的方式输出当前堆中的所有1万个数字。该算法的时间复杂度为O(nmlogm),空间复杂度是常数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值