单峰数组实际上可以看成两个有序的数组,这个问题就转变成了两个有序数组求第k大。
容易想到的算法是对这两个数组进行归并,生成一个新的有序数组,求出第k大之后就可以立刻停止,复杂度是O(k)的。
但是还有更优的算法,可以使用分治的思想(实际上也是一种二分)来计算。
对于两个有序的数组a和b,取出他们第k/2个元素进行比较,这种时候就会产生大于、小于和等于三种情况,对于这三种情况:
1、a的取出元素大于b的取出元素
说明b中的前k/2个元素全部小于a的第k/2个元素,第k大一定不在b的前k/2个中,因此这一部分可以全部丢掉,到剩下的部分中去找第k - k/2大。
2、小于的情况,和1刚好相反,丢弃a的前k/2个,去找剩下的k - k/2。
3、相等的情况。
相等的情况比较复杂,如果k是偶数,那么此刻就已经获得了第k大(a数组k/2个,b数组k/2个,加起来刚好k个),直接返回即可,奇数则需要比较a、b数组的下一个数,返回小的那个。
但是这样讨论比较挫,相等的情况实际上可以被包含在上面两种情况的代码里处理掉,除非空间和时间要求特别大,可以忽略掉。
然后就是某个数组出现了不足以取k/2个的情况,那么很显然,第k大不在这个数组里,可以直接减掉这一部分,去另外一个数组找。
到了最后,k等于1时,就找到了第k大,返回两个数组当前位置的最小值即可。
这样的算法用循环实现起来真的是难写,但是用递归写起来就很舒服了,确定好两个起始位置之后一直往下递归就完事了。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int inf = 1e9 + 7;
int a[maxn], b[maxn], mid, n;
int kth(int s1, int s2, int k) {
if(s1 >= mid) {
return b[s2 + k - 1];
}
if(s2 >= n - mid) {
return a[s1 + k - 1];
}
if(k == 1) {
return min(a[s1], b[s2]);
}
int end1 = inf, end2 = inf;
if(s1 + k / 2 - 1 < mid) {
end1 = a[s1 + k / 2 - 1];
}
if(s2 + k / 2 - 1 < n - mid) {
end2 = b[s2 + k / 2 - 1];
}
if(end1 < end2) {
return kth(s1 + k / 2, s2, k - k / 2);
}
return kth(s1, s2 + k / 2, k - k / 2);
}
/*
8
1 2 4 8 7 3 3 2
*/
int main() {
int k;
cin >> n;
mid = n / 2;
for(int i = 0; i < mid; i++) {
cin >> a[i];
}
for(int i = 0; i < n - mid; i++) {
cin >> b[n - mid - i - 1];
}
while(cin >> k) {
cout << kth(0, 0, k) << endl;
}
return 0;
}