题目链接:https://ac.nowcoder.com/acm/problem/207028
时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
给你一个长度为n的序列,求序列中第k小数的多少。
输入描述:
多组输入,第一行读入一个整数T表示有T组数据。
每组数据占两行,第一行为两个整数n,k,表示数列长度和k。
第二行为n个用空格隔开的整数。
输出描述:
对于每组数据,输出它的第k小数是多少。
每组数据之间用空格隔开
输入样例
2
5 2
1 4 2 3 4
3 3
3 2 1
输出样例
2
3
备注
t≤10,1≤n≤5×10^6,k≤n,数列里每个数都在int范围内
由于输入比较多,请使用快读读取。
例如:
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < ‘0’ || ch > ‘9’){
if (ch == ‘-’)
f = -1;
ch = getchar();
}
while(ch >= ‘0’ && ch <= ‘9’){
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return x * f;
}
分析
首先可以想到用sort排序,然后直接输出第k个元素,代码如下
#include <iostream>
#include <cstdio>
#include <algorithm>
const int N=5e6+5;
int a[N];
using namespace std;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
int t;
int n,k;
t=read();
while(t--)
{
n=read(),k=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+n+1);
cout<<a[k]<<endl;
}
return 0;
}
sort排序的时间复杂度是O(nlogn),这道题使用这种方法,运行时间大概在2500~2800ms,而题目的限定时间是3秒,刚好可以过。再来分析这道题目,我们只需找到序列中第k小数是多少,并不需要将序列全部进行排序。这时候,可以想到用快速排序的思想,将小于和大于某个数的元素分别放到它的左边和右边,而左右两边的数可以是无序的,按照这个思想,可以写出下面的代码
#include <iostream>
#include <cstdio>
#include <algorithm>
const int N=5e6+5;
int a[N];
using namespace std;
int finding(int l,int r,int k)
{
int i=l,j=r;
int mid=(l+r)/2;
int x=a[mid];
while(i<=j)
{
while(a[i]<x) i++;
while(a[j]>x) j--;
if(i<=j)
{
swap(a[i],a[j]);
i++,j--;
}
}
if(k<=j) return finding(l,j,k);
else if(k>=i) return finding(i,r,k);
else return a[k];
}
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
int t;
int n,k;
t=read();
while(t--)
{
n=read(),k=read();
for(int i=1;i<=n;i++)
a[i]=read();
cout<<finding(1,n,k)<<endl;
}
return 0;
}
在finding函数中,以序列中间的元素为参照,大于x的放左边,小于x的放右边,而且x对应元素的位置也可能发生改变。这样如果确定k在某个区间,另一个区间就可以舍弃,运行时间也会减少。这种方法的运行时间大概是1100~1300ms,大约是上一种方法运行时间的一半。
注意
这道题需要使用快读,题目中也给出了提示
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x<<1) + (x<<3) + (ch^48);
ch = getchar();
}
return x * f;
}
解释
1.快读的主要原理是getchar比scanf读取速度快,当进行大规模数据读入的时候,数据的读入就会影响代码的运行时间,此时就要使用快读。
2.在read()函数中,f的作用是标记正负,默认是1,如果读入"-",f变成-1,最终的返回值也会乘f,变为负数。(x<<1) + (x<<3)利用了位运算加速运算速度,相当于将x乘10,(ch^48)是将字符转化为数字。
3.在 C/C++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题。例如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。使用inline修饰带来的好处就是避免了频繁调用函数对栈内存重复开辟所带来的消耗,可以提高函数的执行效率。