[剑指 Offer 40]. 最小的 k 个数

Posted Debroon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[剑指 Offer 40]. 最小的 k 个数相关的知识,希望对你有一定的参考价值。

 


题目

题目:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/

 


函数原型

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {

    }
};

 


快排 partition 思想

完整解析:快速排序之种种

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if(arr.empty() || k == 0) return {};
        vector<int> ans;

        selectK(arr, 0, arr.size()-1, k-1); // 第 k 小数字对应索引是 k-1

        for (int i = 0; i < k; ++i) 
            ans.push_back(arr[i]);
        return ans;
    }

    // 封装 partition
    int partition(vector<int>& arr, int l, int r){
        // 生成 [l, r] 之间的随机索引
        int p = rand() % (r - l + 1) + l;
        swap(arr[l], arr[p]);

        // arr[l+1...i-1] <= v; arr[j+1...r] >= v
        int i = l + 1, j = r;
        while(true) {
            while(i <= j && arr[i] < arr[l])
                i ++;
            while(j >= i && arr[j] > arr[l])
                j --;
            if(i >= j) break;
            swap(arr[i], arr[j]);
            i ++;
            j --;
        }
        swap(arr[l], arr[j]);
        return j;
    }

   void swap( int &a, int &b ){
        int tmp = a;
        a = b;
        b = tmp;
    }

    // 封装 selectK
    int selectK(vector<int>& arr, int l, int r, int k){
        int p = partition(arr, l, r);
        if(k == p) return arr[p];                    // k == p,直接返回
        if(k < p) return selectK(arr, l, p - 1, k);  // k < p,在左边找
        return selectK(arr, p + 1, r, k);            // k > p,在右边找
    }
};

 


优先队列

只需要使用一个优先队列维护当前看到最小的 k 个元素。

对于每一个新元素,如果比这 k 个最小元素中最大的还小,就替换。

实现优先队列的数据结构是,最大堆。

用最大堆维护当前看到的最小 k 个元素,不停的拿这 k 个最小元素中最大值和新元素比较。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> vec(k, 0);
        if (k == 0)                                         // 排除 0 的情况
            return vec;
 
        priority_queue<int> Q;
        for (int i = 0; i < k; ++i) 
            Q.push(arr[i]);

        for (int i = k; i < (int)arr.size(); ++i) 
            if (Q.top() > arr[i]) {
                Q.pop();
                Q.push(arr[i]);
            }

        for (int i = 0; i < k; ++i) {
            vec[i] = Q.top();
            Q.pop();
        }
        return vec;
    }
};

 


快排与优先队列的比较

快排中位数思路:

  • 时间: O ( n ) O(n) O(n)
  • 空间: O ( 1 ) O(1) O(1)

优先队列:

  • 时间: O ( n   l o g k ) O(n~logk) O(n logk)
  • 空间: O ( k ) O(k) O(k)

发现无论时间、空间,快排中位数思路都比优先队列优秀。

但优先队列有一个最大优势,不需要一次性知道所有数据。

  • 我是游戏服务器开发,之前有很多系统需要做查询排名前N的需求。比如战斗力排名前十的玩家,我的做法是有个全局排序的过程,但是这样在玩家很多时,效率会越来越低。当时与同事以及领导商量也没有好的解决方案,于是就妥协采用了全局实时排序。

  • 同时我在团队内普及了这个方案,使得很多类似需求的系统,比如竞技场排名前十、消费前十等等都得到了提升。

以上是关于[剑指 Offer 40]. 最小的 k 个数的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer--40最小的k个数

剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数

剑指Offer40 最小的k个数

LeetCode(剑指 Offer)- 40. 最小的 k 个数

剑指Offer-时间效率面试题40:最小的k个数