一切从一个力扣题开始:最小的k个数
TopK:输入整数数组 arr ,找出其中最小的 k个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
看到有人说STL提供了算法可以解决这个题,自己实现了一下,于是有了这两种解法。
秒杀法
调用STL提供的nth_element方法,该方法让第 n 个位置上的元素就位,且所有在位置 n 之前的元素都小于或等于它,所有在位置 n 之后的元素都大于或等于它。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(!k){
arr.resize(0);
return arr;
}
nth_element(arr.begin(),arr.begin()+k,arr.end());
arr.resize(k);
return arr;
}
};
手写法
感觉时间还可以提,于是决定手写nth_element方法(但是最终用时和内存消耗都大于秒杀法)。
nth_element方法的主要思想是快排划分+三数取中+插排,原始的快排每次快排后都要分别地对左右两个区间进行快排,但是此题只要找前k个数,于是只要把第k个排好即可,因此我们可以略去某些区间的排序。
三数取中是对快排的优化,取当前区间的左、右、中三个数的中位数,把它作为基准元素快排,目的是让左右两个区间尽量均衡,减小最坏情况的用时,让快排的耗时更趋向平均。
插排也是对快排的优化,快排当区间小到一定程度的时候用插入排序会快一些。
此方法使时间复杂度趋于o(n)
class Solution {
public:
typedef vector<int>::iterator vii;
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(!k){
arr.resize(0);
return arr;
}
my_nth_element(arr.begin(),arr.begin()+k,arr.end()-1);
arr.resize(k);
return arr;
}
void my_nth_element(vii left,vii nth,vii right){
while(right-left>32){
vii cur=quickSort(left,right,*mid(left,left+(right-left)/2,right-1));
if(cur<=nth){
left=cur;
}else{
right=cur;
}
}
insertSort(left,right);
}
vii quickSort(vii left,vii right,int pivot){
while(1){
while(*left<pivot)left++;
while(pivot<*right)right--;
if(!(left<right))return left;
swap(left,right);
++left;
}
}
void swap(vii left,vii right){
*left=(*left)^(*right);
*right=(*left)^(*right);
*left=(*left)^(*right);
}
vii mid(vii left,vii m,vii right){
if(*left<*m){
if(*right<*m){
if(*left<*right)return right;//left right m
return left;//right left m
}
return m;
}else{//m left
if(*right>*m){
if(*right<*left)return right;//m right left
return left;//m left right
}
return m;
}
}
void insertSort(vii left,vii right){
while(left<=right){
vii temp=left+1;
while(temp!=right+1){
if(*temp<*left){
swap(temp,left);
}
temp++;
}
left++;
}
}
};
总结:虽然最后没有打过STL提供的方法,但是我对快排的理解更深了一步,能自己实现还是很开心的。