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。每一层需额外空间
。总共
。