题目
问题描述
给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个。
输入格式
第一行包含一个数n,表示序列长度。
第二行包含n个正整数,表示给定的序列。
第三个包含一个正整数m,表示询问个数。
接下来m行,每行三个数l,r,K,表示询问序列从左往右第l个数到第r个数中,从大往小第K大的数是哪个。序列元素从1开始标号。
输出格式
总共输出m行,每行一个数,表示询问的答案。
样例输入
5
1 2 3 4 5
2
1 5 2
2 3 2
样例输出
4
2
数据规模与约定
对于30%的数据,n,m<=100;
对于100%的数据,n,m<=1000;
保证k<=(r-l+1),序列中的数<=106。
题解
快排的原理:利用快速排序将无序序列转换为有序序列的过程中,经过多次的划分;(这里假定进行降序排列)在每一次的划分中,在序列中找到一个枢轴(pivot),划分的结果是 pivot 左边的数均比它大,右边的数均比它小;然后对子序列进行同样的操作,递归进行、直到所有子序列有序为止。
改造:我们的目标是找到第K大的数,若在一次划分之后、pivot左边的数(加上它本身)共有K个,那么pivot位置上的数就是我们要找的第K大的数;其一:我们不必要求前K-1大的数有序,所以不必继续进行递归划分;其二:每一次的划分都将原序列分为左、右两个子序列,快排算法需要对这两个子序列都继续进行递归的划分,而在这里我们只需要根据情况(具体看算法步骤)选择一个子序列继续划分即可;因此这种求第K大的数算法的时间复杂度小于快速排序的复杂度,为O(n*logK)
算法步骤:经过一次划分(partition)之后,枢轴pivot将原序列划分为两个部分:S和T [即原序列变为(S T)、pivot包含在子序列S中、注意使用降序排序],会出现下列三种情况:
1.子序列S中有K个数,此时pivot位置即为第K大的数,算法结束
2.子序列S中的数字个数小于K,假设个数为L,则需要子序列T中继续递归划分出来前(K-L)个数
3.子序列S中的数字个数大于K,则需要子序列S中继续递归划分出来前K个数
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
/*快速排序的一次划分*/
int partition(int a[], int low, int high) {
int i = low, j = high, x = a[low];
while(i<j) {
while(i<j && a[j]<=x) { j--; }
a[i] = a[j];
while(i<j && a[i]>=x) { i++; }
a[j] = a[i];
}
a[i] = x;
return i;
}
/* 降序排序,当一次划分之后、pivot左边的数(包括它本身)恰为k个时,停止划分*/
void find_kmax(int a[], int low, int high, int k) {
if (low < high) {
int poi = partition(a, low, high);// 得到一次划分之后 pivot的位置
int len = poi - low + 1;// 计算子序列S的长度
if (len < k) {// 情况2
find_kmax(a, poi + 1, high, k - len);
} else if (len > k) {// 情况3
find_kmax(a, low, poi - 1, k);
}
}
}
int main() {
int arr[] = {23, 50, 500, 4, 100, 300, 200, 99, 400};
int len = sizeof(arr) / sizeof(int);// 求数组长度
int l, r, k;// 区间为[l, r],从1开始
scanf("%d %d %d", &l, &r, &k);
int a[100];
memcpy(a, arr, sizeof(arr));// 将arr数组的元素copy到a数组中,这么做是为了避免破坏原数组、能够进行多次不同区间的查询
find_kmax(a, l-1, r-1, k);// 注意区间下标与数组下标的转换
printf("%d\n", a[l-1 + k-1]);// 注意第k大的数在数组a中的位置!
return 0;
}
题解二:学了快速排序后的另外一种解法:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e6+10;
int n,k,q[N];
void quick_sort(int q[],int l,int r){
if(l>=r)return;
int i=l-1,j=r+1;
int x=q[l+r>>1];
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main(){
scanf("%d%d",&n,&k);
if(n>=1&&n<1e9+10){
for(int i=0;i<n;i++) scanf("%d",&q[i]);
quick_sort(q,0,n-1);
for(int i=0;i<n;i++){
if(k==i+1) printf("%d",q[n-i-1]);
}
}
return 0;
}