单峰数组求第k大算法

单峰数组实际上可以看成两个有序的数组,这个问题就转变成了两个有序数组求第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
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;
}

你可能感兴趣的:(单峰数组求第k大算法)