题目描述
面试题40:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
方法一:使用C++标准库sort,时间复杂度o(nlogn)
//方法一:使用C++标准库sort,时间复杂度o(nlogn)
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> tmp;
if(input.empty() || k == 0 || k > input.size()) return tmp;
sort(input.begin(), input.end());
tmp.assign(input.begin(), input.begin() + k);
return tmp;
}
};
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
if (input.empty() || !k || k > input.size()) {
return {};
}
sort(input.begin(), input.end(), less<int>());
input.resize(k); // 将a的现有元素个数调至k个,多则删,少则补,其值随机
return input;
}
};
方法二:快排,时间复杂度o(n)
基于快速排序的划分思想,使用partition对数组进行划分;如果基于数组的第k个数字来划分,使得比第k个数字小的所有数字都位于数组的左边,比k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中的左边的k个数字就是最小的k个数字
#if 1
//方法二:快排
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k){
vector<int> output;
int length = input.size();
if(input.empty() || k <= 0 || k > length || length <= 0)
return output;
if(length == k)
return input;
int start = 0, end = length - 1;
int index = partition(input, start, end);
while(index != k - 1 ){
if(index < k -1)
start = index+1;
else
end = index-1;
index = partition(input,start,end);
}
output.assign(input.begin(), input.begin()+k);
return output;
}
int partition(vector<int>& input, int low, int high){
int pivot = input[low];
while(low < high){
while(low < high && pivot <= input[high])
--high;
input[low] = input[high];
while(low < high && pivot >= input[low])
++low;
input[high] = input[low];
}
input[low] = pivot;
return low;
}
};
#elif 0
//对整个序列进行快排
class Solution {
public:
int Partition(vector<int>& v, int low, int high){
int pivot = v[low];
while(low < high){
while(low < high && v[high] >= pivot) high--;
v[low] = v[high];
while(low < high && v[low] <= pivot) low++;
v[high] = v[low];
}
v[low] = pivot;
return low;
}
void QuickSort(vector<int>& v, int low, int high){
if(low < high){
int pivotpos = Partition(v, low, high);
QuickSort(v, low, pivotpos - 1);
QuickSort(v, pivotpos + 1, high);
}
}
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> tmp;
if(input.empty() || k == 0 || k > input.size())
return tmp;
QuickSort(input, 0, input.size() - 1);
tmp.assign(input.begin(), input.begin()+k);
return tmp;
}
};
#endif
方法三:使用堆排序,适合处理海量数据
利用堆排序,特别适用于海量数据中寻找最大或者最小的k个数字。即构建一个大堆容器,初始化大小为k,变量初始数,如初始数组大小小于等于k直接返回,如果大于k,则选择数组的前k个元素,填充堆,然后调整为最大堆。调整完之后,继续从初始数组中拿出一个元素,如果该元素比大堆的堆顶小,则替换堆顶,继续调整为最大堆,如果大于等于堆顶则直接丢弃,不作调整。
PS:大堆还是小堆的选择很重要,不是寻找最小的k个元素就要选择小堆,而且恰恰相反。寻找最小的k个数,其实就是寻找第k个大的元素,即寻找k个数中最大的,不断调整堆,堆得元素个数是k,堆顶是最大值,遍历完初始数组后,堆中存在的元素即使我们所要寻找的k个最小元素。
最大的K个数---- 建小堆
最小的K个数----建大堆
堆排序(手写堆)
//方法三:堆
//用最大堆保存这k个数,每次只和堆顶比,如果比堆顶小,删除堆顶,新数入堆。
#if 1
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> result;
if(input.size() == 0 || k==0 || k > input.size())
return result;
构建最大堆
for(int i = (k >> 1 )- 1 ;i >= 0;i--)//初始化堆 //将初始化堆调整成大根堆
adjustHeap(input,i,k); //堆的元素个数为k
//从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
//最终堆里的就是最小的K个数。
for (int i = k; i <input.size() ; i++) {
if(input[0]>input[i]){
input[0] = input[i];
//swap(input[0],input[i]);
adjustHeap(input,0,k);
}
}
result.assign(input.begin(), input.begin() + k);
return result;
}
#if 0
void adjustHeap(vector<int>&input,int i,int length){ //堆调整
int child=i*2+1;
if(child <= length -1){ //有左孩子
if(child < length-1 && input[child+1]>input[child]) //有右孩子,且有孩子比左孩子大
child=child+1;
if(input[child]>input[i]){
swap(input[child],input[i]);
adjustHeap(input,child,length);
}
}
}
#elif 1 //根节点序号为0开始的堆排序
void adjustHeap(vector<int>&input,int k,int len){
int temp = input[k];
for(int i = 2*k + 1; i <= len -1; i = i * 2 + 1){ //for(int i = 2*k + 1; i <= len -1; i * 2 + 1)为错误语法
if(i < len -1 && input[i] < input[i+1])
++i;
if(temp > input[i])
break;
else{
input[k] = input[i];
k = i;
}
}
input[k] = temp;
}
#endif
};
#endif
/*
//堆排序流程
void Heapsort(vector<int> &input, int length){
for(int i = length/2 - 1; i >= 0; i--){
adjustHeap(input, i, length); //初始化堆
}
for(int i = length-1; i >= 0; i--){
swap(input[i], input[0]);
adjustHeap(input, 0, i); // 重新调整堆
}
}
};
*/
20200910
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> res;
int len = arr.size();
if (arr.empty() || k <=0 || k > len)
return res;
for (int i = (len/2 - 1);i >= 0; --i)
AdjustDown(arr,k,i); //初始化元素个数为k个的最大堆
for (int i = k; i < len; ++i) {
if (arr[0] > arr[i]) {
arr[0] = arr[i];
AdjustDown(arr,k,0);
}
}
res.assign(arr.begin(),arr.begin()+k);
return res;
}
void AdjustDown(vector<int> &A, int len, int k ) //k当前待调整的节点
{
int temp = A[k];
for (int i = 2*k + 1; i <= len - 1; i = 2*i + 1) {
if (i < len - 1 && A[i] < A[i + 1])
++i;
if (temp < A[i]) {
A[k] = A[i];
k = i;
}
else
break;
}
A[k] = temp;
}
};
堆排序(STL)
也可以直接使用STL实现中的堆排序,STL中并没有实现堆这种数据结构,但是algorithm中实现了堆排序算法。主要就是四个函数,make_heap(),pop_heap(),push_heap(),sort_heap()。
- make_heap()
make_heap(first ,last)
make_heap(first ,last, cmpObject)
将[ first, last )范围进行堆排序,默认使用less < int>, 即 :最大元素放在第一个(默认为大根堆)。
- pop_heap()
pop_heap(first ,last)
pop_heap(first ,last, cmpObject)
将front(即第一个最大元素)移动到end的前部,同时将剩下的元素重新构造成(堆排序)一个新的heap
- push_heap()
push_heap(first ,last)
push_heap(first ,last, cmpObject)
对刚插入的尾部元素做堆排序。
- sort_heap()
sort_heap(first ,last)
sort_heap(first ,last, cmpObject)
将一个堆做排序,最终成为一个有序的系列,可以看到sort_heap时,必须先是一个堆(两个特性:1、最大元素在第一个 2、添加或者删除元素以对数时间),因此必须先做一次make_heap.
//20200308,堆STL
class Solution {
public:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
vector<int> res;
if(input.empty() || input.size() < k || k <= 0)
return res;
//初始化vector
res.assign(input.begin(), input.begin() + k);
//for(int i = 0; i < k; ++i){
// res.push_back(input[i]);
//}
//建堆
make_heap(res.begin(), res.end()); //第三个参数默认为less<>(),即大根堆
for(int i = k; i < input.size(); ++i){
if(input[i] < res[0]){
// res[0]出堆,然后再删除
pop_heap(res.begin(), res.end()); //第三个参数默认为less<>(),即大根堆
res.pop_back();
//input[i]入堆
res.push_back(input[i]);
push_heap(res.begin(), res.end());
}
}
return res;
}
};
堆排序(priority_queue)
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
int len = arr.size();
vector<int > res(k,0); //vector<int> res;则无法通过编译?
if (arr.empty() || k <= 0 || k > len)
return res;
priority_queue<int> Q;
for (int i = 0; i < k; ++i) Q.push(arr[i]);
for (int i = k; i < len; ++i) {
if (arr[i] < Q.top()) {
Q.pop();
Q.push(arr[i]);
}
}
for (int i = 0; i < k; ++i) {
res[i] = Q.top();//res.push_back(Q.top());上面使用vector<int>res;则可以通过编译
Q.pop();
}
return res;
}
};
通过assign进行赋值时,目标vector之前可为空,如果通过元素一个个赋值,则应该先初始化目标vector,或通过resize申请空间,或者使用push_back压入元素
方法二和方法三优缺点:比较
思路2
优点:节省空降,时间复杂度平均为O(n)
缺点:需要修改原始数组
思路3
优点:不用修改原始数组,适合海量数据
缺点:时间复杂度略高O(nlogk)