28 最小的K个数
题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路:
解法1:对于小规模数据,可以采用类似前题的快速排序思路,pivot之前的点都是比它小的,之后的点都是比它大的。不管是找中位数还是找前k大,前k小,都可以使用这个方法,平均复杂度是O(N),但是最坏时间复杂度是O(N*N)。这样得到最后k个数是没有进行排序的,所以降低了时间复杂度。
这里需要注意idx初值不该设为0,因为找最小的0个数的时候idx = lo = 0;就不会进入循环当中,所以应该注意39行和50行。这题有个大坑就是k的讨论,k可能小于等于0,也可能大于size,所以必须要进行讨论。
这种方法会对输入的数组进行改变,所以应该要明确告诉面试官这样是否允许。而且这种做法适合小规模数据。
1 class Solution { 2 public: 3 int partition(vector<int> &input,int lo,int hi){ 4 int pos = lo + rand() % (hi - lo + 1); 5 int pivot = input[pos]; 6 swap(input[lo],input[pos]); 7 while(lo < hi){ 8 while(lo < hi){ 9 if(input[hi] > pivot){ 10 --hi; 11 } 12 else{ 13 input[lo] = input[hi]; 14 ++lo; 15 break; 16 } 17 } 18 while(lo < hi){ 19 if(input[lo] < pivot){ 20 ++lo; 21 } 22 else{ 23 input[hi] = input[lo]; 24 --hi; 25 break; 26 } 27 } 28 } 29 input[lo] = pivot; 30 return lo; 31 } 32 vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { 33 if(input.empty() || k > input.size() || k <= 0){ 34 return {}; 35 } 36 vector<int> result; 37 int lo = 0,hi = input.size() - 1; 38 int idx ; 39 idx = partition(input,lo,hi);// 40 int target = k - 1; 41 while(idx != target){ 42 43 if(idx < target){ 44 lo = idx + 1; 45 46 } 47 else if(idx > target){ 48 hi = idx - 1; 49 } 50 idx = partition(input,lo,hi); 51 } 52 for(int i = 0;i < k;++i){ 53 result.push_back(input[i]); 54 } 55 return result; 56 } 57 };
第二种方法:
解法2:O(nlogk)的算法,特别适合处理海量数据。
priority_queue加入和删除操作都是O(logk),top操作是O(1)。默认top的是最大元素。优先级队列本质是基于vector实现的最大堆,因为vector通过下标访问数组,所以top操作复杂度是O(1)的;
STL的set和map还有就是multise和multimap都是基于红黑树实现的,begin是最小元素,即基于最小堆实现的,只有自己写一个仿函数就可以得到最大堆,注意就重载()就行了,stack和queue都是基于deque。红黑树中查找、删除和插入操作的时间复杂度都是O(logk)。
如果要使用仿函数要引入头文件functional。才可以使用greater<int>。
但它是如何保证一棵n个结点的红黑树的高度始终保持在logn的呢?这就引出了红黑树的5个性质:
- 每个结点要么是红的要么是黑的。
- 根结点是黑的。
- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
- 如果一个结点是红的,那么它的两个儿子都是黑的。
- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
#include<iostream> #include<vector> #include<string> #include <cstdio> #include<queue> #include<algorithm> #include<set> using namespace std; template<typename T> struct greater{ bool operator() (const T &a, const T &b) { return a > b; } }; int main() { multiset<int,greater<int>> hashSet; for (int i = 0; i < 4; ++i) {//输入1,3,55,4 int a = 0; cin >> a; hashSet.insert(a); } multiset<int>::iterator iter = hashSet.begin();//得到55 cout << *iter << endl; system("pause"); }
#include<iostream> #include<vector> #include<set> #include<functional> using namespace std; int main() { multiset<int,greater<int>> hashSet; for (int i = 0; i < 4; ++i) {//输入1,3,55,4 int a = 0; cin >> a; hashSet.insert(a); } multiset<int>::iterator iter = hashSet.begin();//得到55 cout << *iter << endl; system("pause"); }
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { vector<int> result; if(input.empty() || k <= 0 || k > input.size()){ return result; } multiset<int,greater<int>> hashSet; multiset<int,greater<int>>::iterator iter = hashSet.begin(); for(int i = 0;i < input.size();++i){ iter = hashSet.begin(); if(hashSet.size() < k){ hashSet.insert(input[i]); } else{ if(*iter > input[i]){ hashSet.erase(*iter); hashSet.insert(input[i]); } } } iter = hashSet.begin(); for(iter;iter != hashSet.end();++iter){ result.push_back(*iter); } return result; } };
解法3:
使用priority_queue实现最大堆。(默认是最大堆,即top等于最大的元素,降序);
底层容器是vector实现的最大堆(stl源码)
模板:priority_queue<int,vector<int>,greater<int>>,升序,最小堆
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { vector<int> result; if(input.empty() || k <= 0 || k > input.size()){ return result; } priority_queue<int> q; for(int i = 0;i < input.size();++i){ if(q.size() < k){ q.push(input[i]); } else{ if(q.top() > input[i]){ q.pop(); q.push(input[i]); } } } for(int i = 0;i < k;++i){ result.push_back(q.top()); q.pop(); } return result; } };