题目:输入n个整数,找出其中最小的k个数。例如,输入3,6,7,2,1,5,6个数字,则最小的3个数字是1,2,3。
1.首先是用最简单的方法: 把输入的n个整数排序,排序之后位于最前面的k个数就是最小的k个数。这种思路的时间复杂度是O(nlogn),显然时间复杂度太大。
2.我们可以使用排速排序中的思想,使用partition函数进行排序,如果基于第k个数字来调整,则使得比第k个数小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。
接下来我们来编写C++的代码:
void GetLeastNumbers(int* input, int n, int* output, int k)
{
if(input == nullptr || output == nullptr || k > n || n <= 0 || k <= 0)
return;
int start = 0;
int end = n - 1;
int index = partition(input, n, start, end);
if(index != k-1)
{
if(index > k -1)
{
end = index - 1;
index = partition(input, n, start, end);
}
else
{
start = index + 1;
index = partition(input, n, start, end);
}
}
for(int i =0; i < k; i++)
output[i] = input[i];
}
这种思路的时间复杂度为O(n),但是采用这个思路有一个限制,那就是我们需要修改输入的数组,因为函数partition会调整数组中数字的顺序。如果面试官要求不能修改数组的话,我们就只能另寻他法。
3.我们可以先创建一个大小为k的数据容器来存储最小的k个数字,接下来每次从输入的n个整数中读入一个数。如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中;如果容器中已有k个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字。找出这已有的k个数中的最大值,然后拿这次待插入的整数和最大值进行比较。如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个数之一,于是我们可以抛弃这个整数。
所以,当容器满的时候,我们要进行三个步骤:
- 在k个整数中找到最大数。
- 有可能在这个容器中删除最大数。
- 有可能要插入一个新的数。
如果用一个二叉树来实现这个数据容器,那么我们能在O(logk)时间内实现这三个步骤。所以对于n个输入数字而言,总的时间效率为O(nlogk)。
这里我们选用最大堆。
接下来我们编写C++代码:
typedef multiset<int, greater<int>> intSet;
typedef mulyiset<int, greater<int>>::iterator setIterator;
void GetLeastNumbers(const vector<int>& data, intSet& leastNumbers, int k)
{
leastNumbers.clear();
if(k < 1 || data.size() < k)
return;
vector<int>::const_iterator iter = data.begin();
for(; iter != data.end(); ++iter)
{
if((leastNumbers.size()) < k)
leastNumbers.insert(*iter);
else
{
setIterator iterGreatest = leastNumbers.begin();
if(*iter < *(leastNumbers.begin()))
{
leastNumbers.erase(iterGreatest);
leastNumbers.insert(*iter);
}
}
}
}
扩展:
我们还可能在面试的过程中遇到《找出最大的k个数》这样的变形题,他们的思路基本上是一样的,就是将对大堆改为最小堆。