面试题30:最小的k个数
题目描述:
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
题目分析:
找出最小的k个数,直观的解法从小到大排序,取前k个即可,时间复杂度是O(NlogN),显然方法不够好。
1. 时间复杂度O(N)的解法
思路1:
前面已经可以得出,采用基于快速排序Partition过程有时间复杂度O(N)的解法,最小的k个数,不需要有序,明白快速排序的话,就好说了,直接上代码。
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
int len = input.size();
if (len < k || len == 0)
return res;
int left = 0;
int right = len - 1;
int pos = Partition(input, left, right);
while (pos != k) {
if (pos < k) {
left = pos + 1;
pos = Partition(input, left, right);
} else {
right = pos - 1;
pos = Partition(input, left, right);
}
}
for (int i = 0; i < k; i ++) {
res.push_back(input[i]);
}
return res;
}
int Partition(vector<int> &nums, int left, int right) {
int pivot = nums[left];
while (left < right) {
while (left < right && nums[right] >= pivot)
-- right;
nums[left] = nums[right];
while (left < right && nums[left] < pivot)
++ left;
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
};
2. 堆排序
思路2:
堆排序很适合来求top k问题,尤其当数据量很大时,时间复杂度是O(Nlogk)。
先创建一个大小为 k 的数据容器来存储最小的 k 个数字,接下来我们每次从输入的 n 个整数中读入一个数.如果容器中已有的数字少于 k 个,则直接把这次读入的整数放入容器之中:如果容器中己有k 数字了,也就是容器己满,此时我们不能再插入新的数字而只能替换已有的数字。找出这己有的 k 个数中的最大值,然后这次待插入的整数和最大值进行比较。如果待插入的值比当前己有的最大值小,则用这个数替换当前已有的最大值:如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个整数之一,于是我们可以抛弃这个整数。
因此当容器满了之后,我们要做 3 件事情: 一是在 k 个整数中找到最大数: 二是有可能在这个容器中删除最大数: 三是有可能要插入一个新的数字。我们可以使用一个大顶堆在 O(logk)时间内实现这三步操作。
实现代码1:
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
int len = input.size();
if (len < k || len == 0)
return res;
int i;
for (i = 0; i < k; i ++)
res.push_back(input[i]);
BuildMaxHeap(res, k);
/* 从第k到最后,开始不断调整堆*/
for (i = k; i < len; i ++) {
if (input[i] < res[0]) {
res[0] = input[i];
AdjustHeap(res, 0, k);
}
}
return res;
}
void BuildMaxHeap(vector<int> &nums, int len) {
for (int i = len / 2 - 1; i >= 0; i --)
AdjustHeap(nums, i, len);
}
void AdjustHeap(vector<int> &nums, int i, int len) {
int child = 2 * i + 1;
int parent = nums[i];
while (child < len) {
if (child + 1 < len && nums[child] < nums[child + 1])
++ child;
if (parent < nums[child]) {
nums[i] = nums[child];
i = child;
child = 2 * i + 1;
} else {
break;
}
}
nums[i] = parent;
}
};
不知道为什么在牛客网上会报错,段错误:您的程序发生段错误,可能是数组越界,堆栈溢出(比如,递归调用层数太多)等情况引起,没有找到错误地方。
实现代码2:
使用优先队列,优先队列的结构类似于堆,默认是最大堆,top()是最大堆的最大值,堆中的第一个元素,push(val)会自动调用push_heap来调整堆,pop()是将堆中的最大值出队列。
优先队列的模板声明带有三个参数,priority_queue< Type, Container, Functional>Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list.
STL里面默认用的是 vector. 比较方式默认用 operator< , 所以如果你把后面两个参数缺省的话,优先队列就是大顶堆,队头元素最大。
参考:
http://www.cplusplus.com/reference/queue/priority_queue/
http://www.cppblog.com/Darren/archive/2009/06/09/87224.html
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(k < 1 || k > input.size())
return res;
priority_queue<int> pq;
int i = 0;
int len = input.size();
while (i < len) {
if (pq.size() < k)
pq.push(input[i]);
else {
if (pq.top() < input[i]) {
++ i;
continue;
} else {
pq.pop();
pq.push(input[i]);
}
}
++ i;
}
while (!pq.empty()) {
res.push_back(pq.top());
pq.pop();
}
return res;
}
};
实现代码3:可以采用multiset,先写到这里了,回来补充。