求两个有序数组合并后的第K大元素

问题描述

有两个数组A和B,假设A和B已经有序(从大到小), 求A和B数组中所有数的第K大元素。

问题分析

在寻找第K大元素时,可以使用归并排序,对两个数组进行合并,合并到第K个元素时,即为所求值,使用此方法的时间复杂度为 O ( K ) O(K) O(K),此方法并不是最优的方法,考虑使用分治法。对于任意两个元素,若
A i > B j (1) A_i > B_{j} \tag{1} Ai>Bj(1)
则说明 A i A_i Ai之前至少有i-1个数,而 B j B_j Bj之前至少有 i + j − 1 i + j - 1 i+j1个数,同时,当
A i ≤ B j − 1 (2) A_{i} \le B_{j - 1} \tag{2} AiBj1(2)
说明 A i A_i Ai在合并后的数组中的位置一定为第 i + j − 1 i + j - 1 i+j1。所以在寻找第K大元素时,若该元素在数组A中,则该满足公式(1)和公式(2)的同时,满足
i + j − 1 = K (3) i + j - 1 = K \tag{3} i+j1=K(3)

考虑若元素在数组A中时,需要寻找一对序偶 ( i , j ) (i,j) (i,j)满足公式(1)、(2)、(3)。既,我们只需要枚举 i i i的值, j j j的值可以通过公式(3)求解出,判断 A i A_i Ai是否满足(1), (2)两个不等式关系,若满足这两个关系则,该数就是我们需要寻找的第 k k k个元素。

在进行枚举时,根据大小关系,我们可以优化 i i i值的寻找。
若该元素不在数组A中,则一定在数组B中,所以我们先假设该元素在数组A中,在A中进行寻找,当数组A中寻找失败时,再在数组B中寻找即可。
在数组A中寻找,当 ( i , j ) (i,j) (i,j)满足(1)和(3),但是不满足(2)时,说明 B j B_j Bj之前至少有 i + j − 1 i + j - 1 i+j1个数,既 B j B_j Bj之前至少有 k k k个数, A i A_i Ai在合并后的数组中的位置为 i − 1 < s i t e < k i - 1 < site < k i1<site<k,也就是说 A i A_i Ai比第 k k k个元素的数值要大,则该元素的位置一定在 i i i之后,在下一次寻找时,寻找区间可以缩减到 i i i之后。反之则K大元素在 i i i之前,在前半段区间进行寻找。

流程设计

根据之前的分析,可以使用分治法,通过二分进行查找。查找区间 [ a , b ] [a,b] [a,b]的流程图如下:

请添加图片描述

使用二分法在数组A中持续寻找,最后可能没有寻找到时,返回寻找失败,流程图中返回nan,当在数组A中寻找失败时,按照相同的方法在数组B中进行寻找,即可完成最终的任务。

使用上述算法进行寻找时,可以发现,我们的每次寻找都在逐步缩小范围,每次缩小为原来范围的一半,算法复杂度分析为 O ( 2 l o g K ) = O ( l o g K ) O(2logK) = O(logK) O(2logK)=O(logK)。将原来的线性时间复杂度缩减为对数时间复杂度。

代码实现与测试

测试两个数组(13, 10, 9, 6, 4, 2),(15, 11, 8, 7, 5, 3)。实现如下:

#include<iostream>
#include<cmath>

using namespace std;

const int N = 100;
int n;
int k;
int cnt = 0;

int A[2][N];

int dfs(int al, int ar, int arr)
{
	if (al > ar)
		return -1;
	cnt++;
	int amid = (al + ar) / 2;
	int bmid = k + 1 - amid;
	if (A[arr][amid] >= A[!arr][bmid])
	{
		if (A[arr][amid] <= A[!arr][bmid - 1])
			return A[arr][amid];
		return dfs(amid + 1, ar, arr);
	}
	else
	{
		if (A[!arr][bmid] <= A[arr][amid - 1])
			return A[!arr][bmid];
		return dfs(al, amid - 1, arr);
	}
}

int main()
{
	A[0][0] = INT_MAX;
	A[1][0] = INT_MAX;
	int a[] = {13, 10, 9, 6, 4, 2};
	int b[] = {15, 11, 8, 7, 5, 3 };
	int La = sizeof a / sizeof (int);
	int Lb = sizeof b / sizeof(int);
	memcpy_s(A[0] + 1, sizeof a, a, sizeof a);
	memcpy_s(A[1] + 1, sizeof b, b, sizeof b);
	while (cin >> k)
	{
		cnt = 0;
		int ans = dfs(1, min(k, La + 1), 0);
		if (ans == -1)
		{
			//不在数组A里面
			ans = dfs(1, min(k, Lb + 1), 1);
		}
		cout << ans << endl;
		cout << "寻找次数: " << cnt << endl;
	}
	return 0;
}

测试结果为

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值