jz29 最小的K个数
题意分析
找出数组中最小的K个数
示例输入:input = [4,5,1,6,2,7,3,8],4
返回:[1,2,3,4]
解释:很明显输入数组最小的4个元素为1,2,3,4。
题解一(堆)
堆可以看做是一个二叉树。其父节点和子节点有这一定的数量关系。
大根堆:也叫最大堆,每个父节点大于其子节点。
小根堆:也叫最小堆:每个父节点小于其子节点。
对于本题,我们使用最大堆。在c++中,可以使用 priority_queue作为声明一个大根堆。
算法过程:首先将input数组前k个数插入到大根堆中,之后的数依次和大根堆的堆顶进行比较。如果当前的数比堆顶的数要小,那么就把堆顶的弹出,把当前的数插入。如果比堆顶的元素大,pass这个当前的数。
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
if (k == 0 || k>input.size()) {
return {};
}
priority_queue<int> q;
for (int i = 0; i < k; ++i) {
q.push(input[i]);
}
for (int i = k; i < input.size(); ++i) {
if (q.top() > input[i]) {
q.pop();
q.push(input[i]);
}
}
vector<int>ret(k,0);
for(int i=0;i<k;i++) {
ret[i] = q.top();
q.pop();
}
return ret;
} 时间复杂度:。对优先队列做一次插入时间复杂度为
。最坏的情况n个数都要插入到优先队列中,总的时间复杂度为
。
空间复杂度:。使用了大小为k的优先队列
题解二(快排的Select 部分)
快排的划分函数会根据Pivot元素将输入数组分成两部分。小于Pivot会放在左边,大于Pivot会放在右边。然后返回Pivot的下标。
我们可以使用一次划分函数,划分一次。
如果划分后的index小于K,说明在Pivot的右边还有需要的元素,接着使用划分函数。
如果划分后的index大于K,说明在Pitvo的左边还有需要的元素,接着使用划分函数。
举个例子:
input = [4,5,1,6,2,7,3,8],4
找到前4个数
int partition(vector<int>& input, int l, int r) {
//以l-r区间的最后一个元素作为pivot划分
int pivot = input[r];
int i = l - 1;
for (int j = l; j <= r - 1; ++j) {
if (input[j] <= pivot) {
i = i + 1;
//小于pivot与pivot发生交换
swap(input[i], input[j]);
}
}
swap(input[i + 1], input[r]);
//返回小于pivot的第一个元素的下标
return i + 1;
}
void selected(vector<int>& input, int l, int r, int k) {
//选择部分,选择input的l-r区间的前k小的数
if (l >= r) {
return;
}
int pos = partition(input, l, r);
int num = pos - l + 1;
if (k == num) {
//当划分的位置等于要求的第k小的数,划分结束
return;
} else if (k < num) {
//当划分的位置大于要求的第k小的数,应更新有区间端点r = pos-1
selected(input, l, pos - 1, k);
} else {
//当划分的位置小于要求的第k小的数,应更新有区间端点l = pos-1
selected(input, pos + 1, r, k - num);
}
}
vector<int> GetLeastNumbers_Solution(vector<int>& input, int k) {
if(k==0||k>input.size()){
return {};
}
selected(input, 0, (int)input.size() - 1, k);
vector<int> ret;
return vector<int>(input.begin(),input.begin()+k);
}
时间复杂度:,这里我们每次递归的时候,区间是没有重合的。我们扫过的区间长度为n。
空间复杂度:。最坏情况递归深度为n。每一层需额外空间
。总共
。

京公网安备 11010502036488号