TopK题型总结

几个典型题目:
1、面试题 17.14. 最小K个数
2、215. 数组中的第K个最大元素
3、给定一个数组求中位数
此类题一般有堆、快速选择、排序三种方案。

1.最小K个数

题目:

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
提示:

0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))
  1. 最简单直接的想法就是使用快排,然后将最小的K个数取出来即可,代码就不放了。虽然复杂度已经可以接受了,但是如果在面试的时候,面试官还是不会满意的。
    时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  2. 堆。维护一个堆的时间复杂度是 O ( l o g n ) O(logn) O(logn),在本题中需要维护一个大小为 k k k的大顶堆,然后遍历数组中的每一个元素,如果堆大小小于 k k k,那么直接将元素push到堆中,如果大于等于 k k k,那么应该判断堆顶元素是不是大于当前数组元素,如果大于那么将堆顶元素弹出,将当前元素push到堆中;如果不是则什么都不做。
    时间复杂度: O ( n l o g k ) O(nlogk) O(nlogk)
    代码:
    vector<int> smallestK(vector<int>& arr, int k) {
        vector<int> res;
        if(k==0) return res;
        priority_queue<int> pq;
        for(auto num:arr) 
        {
            if(pq.size()>=k){
                if(num<pq.top()){
                    pq.pop();
                    pq.push(num);
                }
            }else{
                pq.push(num);
            }
        }
        while(!pq.empty()){
			res.push_back(pq.top());
			pq.pop();
		}
		return res;
    }

2.数组中的第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 ≤ 数组的长度。
  1. 本题本质上与上一题一样,利用快排或者维护一个大小为 k k k的小顶堆,返回第 k k k个最大元素即可。算法复杂度上题已经分析过了。但是本题与上一题的最大区别就是我们只需要返回第 k k k个最大元素,而与其他元素无关,可以看到,如果使用快排那么是将整个数组进行排序,利用堆是将最大的 k k k个元素全部找出来,而对于本题这些操作都是冗余的。面试官也不会满意。
  2. 快速选择。为了进一步降低算法复杂度,可以采用快速选择算法。快速选择算法基于快速排序算法,在快速排序算法中,会定义一个基准,每一轮将小于基准的数和大于基准的数分别放在基准的两边,然后同时将两边的子序列进行递归。而在本题中,可以看到有 k k k个以上的数都大于这个基准了,说明我们要的结果一定不会在基准的另一侧,所以每次只需要递归一边就可以了。
    时间复杂度: 平均下来 O ( n ) O(n) O(n)(最坏 O ( n 2 ) O(n^2) O(n2)
    代码:
int quick_select(vector<int>& q, int l, int r, int k)
    {
        if(l>=r) return q[l];
        int x=q[(l+r)>>1], i=l-1, j=r+1;
        while(i<j)
        {
            do i++; while(q[i]>x);//如果题目改成最小的第k个元素改变一下符号即可
            do j--; while(q[j]<x);
            if(i<j) swap(q[i], q[j]);
        }
        if(j-l+1>=k) return quick_select(q, l, j, k); //判断是不是已经有k个数字大于基准了
        else return quick_select(q, j+1, r, k-(j-l+1));//如果取后半部分,注意对于后面的k值已经改变了
    }
    
    int findKthLargest(vector<int>& nums, int k) {
        return quick_select(nums, 0, nums.size()-1, k);
    }

3. 给定一个数组求中位数

无序数组求中位数也是面试中经常问的问题,有了前两题的基础,这一题应该很快就能解决。

  1. 快排。排序后,如果数组大小为奇数,我们就令 k = l e n g t h / 2 k=length/2 k=length/2,取出下标为k的元素返回,如果数组长度为偶数我们取下标为 l e n g t h / 2 − 1 和 l e n g t h / 2 length/2 - 1和length/2 length/21length/2的元素取平均返回。
  2. 堆。同理,建立一个大小为 l e n g t h / 2 length/2 length/2堆,如果数组长是奇数,直接返回堆顶元素,如果是偶数,返回当前堆顶和pop后的堆顶的平均值。
  3. 快速选择。同理如果是偶数我们需要调用快速选择函数2次取平均,如果是奇数只需要一次即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值