题目描述
输入 n(n<5000000 且 n 为奇数) 个数字 a_i(0<a_i<10^9) 输出这些数字的第 k 小的数。最小的数是第 0 小。
输入格式
无
输出格式
无
输入输出样例
输入
5 1
4 3 2 1 5
输出
2
思路与解法
看到这题首先想到的是用快排排序,然后输出第k个元素即可,但是算了下时间,快排的时间复杂度是O(nlogn),带入5000000大约为1.11*10^8,刚好会超时(不过用O2能过),所以还需要再优化。
目的是找到第k小的数,所以对所有数排序,显然是做了些多余的功夫,那么我们可以沿用快排的思路,先随机找到一个数作为中间值m,遍历查找范围L-R,范围中比m小的数放在m左边,比m大的数放在m右边,和m相等的数也会自然聚在中间,那么,左边的数就为数组第L小的数~第(左边与中间交界值)小的数,中间和右边以此类推,那么就只需要拿k和三段间的交界下标比较就知道第k小的数在哪个范围了,用那个范围继续递归查找,直到k等于中间值或查找范围为1为止。递归代码如下:
int arr[int(5E6)+5];
int func(int l,int r,int k){
if(l==r)return arr[l];
int li = l-1,ri = r+1,mNum = arr[int(rand()/double(RAND_MAX)*(r-l+1)+l)]; //[l,r]
for(int i = l;i <= r&&i<ri;i++){
if(arr[i]<mNum)swap(arr[i],arr[++li]);
else if(arr[i]>mNum)swap(arr[i],arr[--ri]),i--;
}
if(k<=li)return func(l,li,k);
else if(k>=ri)return func(ri,r,k);
else return arr[li+1];
}
第一次查找时需要遍历n个数,第二次遍历n/2个数,第三次n/4,第四次n/8…,加起来趋近于2n,所以这个解法的时间复杂度为O(n)。
但因为输入很大,所以不能直接用C++的读写(会超),可以用快读:
int read(){
char c=getchar();int r=0;
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
r=r*10+c-'0';
c=getchar();
}
return r;
}
或者c语言读写,或者关闭同步提高C++读写速度:
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for(int i = 0;i < n;i++)cin >> arr[i];
都可。
完整代码
#include<iostream>
#include<algorithm>
using namespace std;
int arr[int(5E6)+5];
int func(int l,int r,int k){
if(l==r)return arr[l];
int li = l-1,ri = r+1,mNum = arr[int(rand()/double(RAND_MAX)*(r-l+1)+l)]; //[l,r]
for(int i = l;i <= r&&i<ri;i++){
if(arr[i]<mNum)swap(arr[i],arr[++li]);
else if(arr[i]>mNum)swap(arr[i],arr[--ri]),i--;
}
if(k<=li)return func(l,li,k);
else if(k>=ri)return func(ri,r,k);
else return arr[li+1];
}
int main(){
int n,k;
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for(int i = 0;i < n;i++)cin >> arr[i];
cout << func(0,n-1,k);
return 0;
}