剑指offer 40. 最小的k个数(快速选择;堆排序;红黑树。遇到海量数据时选择哪种方法?)

news/2024/5/19 21:28:59 标签: 快速排序, , 算法, topk

2020年12月25日 周五 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】


本文目录

    • 题目简介
    • 1. 基于快排的快速选择(quick select)方法(减治)
    • 2. 最大(前k小)/ 最小(前k大)
      • 2.1 手动实现最大
      • 2.2 使用STL中的优先队列 priority_queue
    • 3. 红黑树
    • 4. 总结(遇到海量数据时选择哪种方法?)
    • 参考文献


题目简介

剑指 Offer 40. 最小的k个数
在这里插入图片描述
经典TopK问题,雷同题目: LeetCode 215. 数组中的第K个最大元素(快排、排的应用)

1. 基于快排的快速选择(quick select)方法(减治)

快速选择(quick select)方法是快排的变形,通过递归执行partition操作,找出第k大元素的索引idx,然后返回[0,idx]即为最小的k个数。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
    	// 处理特殊情况
        if(k==arr.size() || arr.empty()) return arr;
        if(k==0) return {};
        int len = arr.size();
        // 设置随机数种子
        srand(time(0));
        // 寻找第k大的元素索引idx,然后返回[0,idx]即为最小的k个数
        return quickSelect(arr,0,len-1,k);
    }

    // 基于快排的选择方法(quick select)
    vector<int> quickSelect(vector<int>& arr, int l, int r, int k){
        int rand_idx = rand() % (r-l+1) + l;
        swap(arr[l],arr[rand_idx]);

        int idx = partition(arr,l,r);
        // 返回最小的k个数
        if(idx==k) return vector<int>(arr.begin(), arr.begin()+k);
        else return idx<k?quickSelect(arr,idx+1,r,k):quickSelect(arr,l,idx-1,k);
    }

    // 分割操作(以中轴元素为基准,将数组分割成三部分,返回中轴元素索引)
    int partition(vector<int>& arr, int l, int r){
        int pivot = arr[l]; // 取第一个位置的元素作为中轴元素

        // 右边主动接近左边,进行循环覆盖
        while(l < r){
            // 从右边寻找第一个大于 pivot 的数
            while(l < r && arr[r] > pivot) --r; 
            // 找到后直接进行覆盖
            arr[l] = arr[r];
            // 从左边寻找第一个小于 pivot 的数
            while(l < r && arr[l] <= pivot) ++l; 
            // 找到后直接进行覆盖
            arr[r] = arr[l];
        }

        // 把pivot交换到中间,完成分割(因为是右边主动接近左边,所以这里是arr[l])
        arr[l] = pivot;
        return l;
    }
};
  • 时间复杂度:由于引入了随机化,时间代价的期望是 O ( n ) O\left( {n} \right) O(n)
  • 空间复杂度:递归调用的期望深度为 O ( l o g n ) O\left( {logn} \right) O(logn)

2. 最大(前k小)/ 最小(前k大)

本题也可以用最大:根据数组前 k 个元素建立一个大小为 k 的最大,然后遍历剩下的数组元素,与顶元素进行比较,动态调整中的元素,数组遍历结束后的中元素就是数组中前 k 小的元素。

2.1 手动实现最大

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k)  {
        // 对数组中前k个元素建立最大
        for(int i=k/2-1;i>=0;--i)
            heapAdjustDown(arr,i,k);

        // 从第k+1个元素开始遍历数组,将数组元素与顶元素进行比较
        for(int i=k;i<arr.size();++i){
            // 小于顶元素,则替换之
            if(arr[i]<arr[0]){
                swap(arr[0],arr[i]);
                heapAdjustDown(arr,0,k);
            }
        }
        // 遍历结束后,里的元素即为前k小元素
        return vector<int>(arr.begin(), arr.begin()+k);
    }

    // 向下调整(常用于删除元素)
    void heapAdjustDown(vector<int>& arrs, int parent, int curLen){
        int curParent = arrs[parent];
        int child = 2 * parent + 1; // 左孩子
        while (child < curLen) {  // 孩子不存在,跳出循环
            if (child + 1 < curLen && arrs[child] < arrs[child + 1])
                ++child;       // 右孩子的下标
            if (curParent < arrs[child]) {
                // 大值覆盖父结点,大值上浮,给小值腾出空间,类似简单插入排序(大值右移)
                arrs[parent] = arrs[child];
                parent = child;
                // 迭代子树,因为如果子树中有比curParent大的,curParent还要继续下沉
                child = 2 * parent + 1;
            }
            else break;
        }
        // 迭代结束,这时的p位置即为curParent应该在的位置
        arrs[parent] = curParent;
    }
};

2.2 使用STL中的优先队列 priority_queue

priority_queue 的底层实现就是,所以可以直接借助 priority_queue 解决问题。

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k)  {
        vector<int> res(k,0);
        if(k==0) return res;
           
        priority_queue<int> q;
        // 数组前k个元素入
        for(int i=0;i<k;++i)
            q.push(arr[i]);

		// 根据数组中剩下元素更新
        for(int i=k;i<arr.size();++i){
            if(arr[i]<q.top()){
                q.pop();
                q.push(arr[i]);
            }
        }

		// 取出内元素
        for(int i=0;i<k;++i){
            res[i] = q.top();
            q.pop();
        }
        return res;
    }
};

在这里插入图片描述

3. 红黑树

借助STL中的mutilset来实现,过程和使用priority_queue 实现很像。
在这里插入图片描述

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k)  {
        vector<int> res(k,0);
        if(k==0) return res;

        // 指定 multiset 从大到小排序      
        multiset<int,greater<int>> ms;
        // 数组前k个元素加入multiset
        for(int i=0;i<k;++i)
            ms.insert(arr[i]);
		// 根据数组中剩下元素更新multiset
        for(int i=k;i<arr.size();++i){
            if(arr[i]<*ms.begin()){
                ms.erase(ms.begin());
                ms.insert(arr[i]);
            }
        }
		// 取出multiset中的元素
        copy(ms.begin(), ms.end(), res.begin());
        return res;
    }
};

时间和空间复杂度同最大

4. 总结(遇到海量数据时选择哪种方法?)

其实方法2和方法3都属于树的范畴,可以看作是一种方法,书上总结了这两种方法的优缺点,一定要牢记。
在这里插入图片描述
另外,海量数据的TopK问题可以参考这篇文章:【面试现场】如何在10亿数中找出前1000大的数


参考文献

《剑指offer 第二版》

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


http://www.niftyadmin.cn/n/559451.html

相关文章

数学简单专题

http://vjudge.net/vjudge/contest/view.action?cid52710#overview A - Cube HDU 1220 简单推导 1 #include <map>2 #include <set>3 #include <stack>4 #include <queue>5 #include <cmath>6 #include <ctime>7 #include <vector>…

剑指offer 38. 字符串的排列(回溯算法,递归思想)

2020年12月29日 周二 天气晴 【不悲叹过去&#xff0c;不荒废现在&#xff0c;不惧怕未来】 1. 题目简介 剑指 Offer 38. 字符串的排列 2. 回溯算法 回溯算法&#xff0c;递归思想&#xff0c;总给人一种无从下手的感觉。其实这道题的核心思想就是&#xff1a;当前位和其它…

子类化和超类化区别(介绍Windows的窗口、消息、子类化和超类化)(转)

原文地址&#xff1a;http://maqianli210.blog.sohu.com/75497589.html 这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词。为了叙述的完整性而讨论了Windows的窗口和消息&#xff0c;也简要讨论了进程和线程。子类化&#xff08;Subclassing&#xff09;和超…

C++类的实例化加括号和不加括号的区别

2020年1月7日 周四 天气晴 【不悲叹过去&#xff0c;不荒废现在&#xff0c;不惧怕未来】 直接说结论吧&#xff1a; 类本身带有无参构造函数&#xff0c;则实例化时&#xff0c;两种方法成员变量都会初始化。 class Test { private:int m_Int;int *m_pInt;public:Test(){} }…

PDO 操作实例全解

PDO 操作实例全解 <?php$dsn "uri:pdo_dsn.ini";//"mysql:host127.0.0.1;dbnametest;port3306";$user "root";$pass "123456";try{$pdo new PDO($dsn,$user,$pass);//$in $pdo -> exec("insert into user values …

剑指offer 44. 字符串的排列(迭代 + 求整 / 求余)

2021年01月08日 周五 天气晴 【不悲叹过去&#xff0c;不荒废现在&#xff0c;不惧怕未来】 本文目录1. 题目简介2. 迭代 求整 / 求余2.1 解题思路2.2 代码参考文献1. 题目简介 剑指 Offer 44. 数字序列中某一位的数字 2. 迭代 求整 / 求余 2.1 解题思路 以下解题思路来…

剑指offer 45. 把数组排成最小的数(自定义排序,关键在证明)

2021年01月09日 周六 天气晴 【不悲叹过去&#xff0c;不荒废现在&#xff0c;不惧怕未来】 本文目录1. 题目简介2. 自定义排序2.1 解题思路2.2 代码参考文献1. 题目简介 剑指 Offer 45. 把数组排成最小的数 2. 自定义排序 2.1 解题思路 以下解题思路来自大佬 Krahets&#…

解释往Xcode拖资源出现的界面

解释往Xcode拖资源出现的界面&#xff0c;用下面的图片来解释 转载于:https://www.cnblogs.com/xjblog/p/3940643.html