题目:
输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8,则最小的4个数字是1、2、3、4。
常见思路:
这道题最简单的思路就是先把输入的n个整数排序,排序之后位于最前面的k个数就是最小的k个数。常见的排序算法都可以使用,时间复杂度就是排序的时间复杂度,较好的时间复杂度为O(nlogn)O(nlogn)O(nlogn),这里顺便提一下python中的内置sort函数,使用的是蒂姆排序,结合了归并排序和插入排序,最坏的时间复杂度为O(nlogn)O(nlogn)O(nlogn)。
下面介绍几种时间复杂度小于O(nlogn)O(nlogn)O(nlogn)的算法。
1.O(nlogk)O(nlogk)O(nlogk)的算法:
这种思想主要就是维护一个大小为k的数据容器;首先创建一个大小为k的数据容器来存储最小的k个数字,接下来我们每次从输入的n个整数中读取一个数,比较待插入的整数和容器中的最大值,如果比已有的最大值小,就插入容器替换这个最大值;否则就不进行操作,因为这个数比容器的最大值还要大。
我们需要对容器做的操作有:
- 在k个整数中找到最大数。
- 在容器中删除最大值。
- 插入一个新的数字。
如果用一个二叉树来实现这个数据容器,那么我们可以在O(logk)O(logk)O(logk)时间内实现这三个步骤。因此对于n个输入数字而言,总的时间效率就是O(nlogk)O(nlogk)O(nlogk),这种思路特别适合处理海量数据。
这个数据容器有多种实现方式,下面介绍主要的几种实现方式。
1.1 最大堆:
我们可以选择不同的二叉树来实现这个数据容器。在最大堆中,根结点的值总是大于它的子树中任意结点的值。于是我们可以在O(1)O(1)O(1)时间得到已有的k个数字中的最大值,但需要O(logk)O(logk)O(logk)时间完成删除及插入操作。
下面是实现的代码:通过数组模拟最大堆。
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(input.empty() || input.size() < k || k <= 0) return res;
for(int i = 0; i < k; ++i){
res.push_back(input[i]);
}
for(int i = k/2-1; i >= 0; i--){
// 初始化堆
adjustHeap(res, i, k);
}
for(int i = k; i < input.size(); i++){
if(input[i] < res[0]){
// 存在更小的数字时
res[0] = input[i];
adjustHeap(res, 0, k)