寻找两个不等长数组的中位数 Median of Two Sorted Arrays

142 篇文章 20 订阅
51 篇文章 0 订阅

问题:There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

给定两个有序数组,长度不一定相同,一个是m、一个是n,要求给出他们合并在一起之后的数组的中位数。

要求时间复杂度为O(log(m+n)),所以不可以合并数组再找中位数,否则复杂度就是O(m+n)。


数组的中位数

如果有序数组有奇数个元素,那么中位数是最中间那个元素。

如果有序数组有偶数个元素,那么中位数是中间两个元素的平均数。


思路:

    这道题目看起来容易,本以为二分递归就行了。但是写了好久都写不出来。因为情况比较复杂,当两个数组元素个数都为奇数或偶数,或一奇一偶时,当各自中位数相同或不同时,情况都不一样。中位数如果是两个数字的平均数,这两个数字可能来自于同一个数组,也可能来自两个数组。总之是很混乱。写了大概有好几个小时,始终不能搞定。觉得这个思路看来不对。只能google了。

    看到别人对这道题的思路,不是二分查找直接找中位数,而是将核心思想转化为去找两个数组的从小到大排的第k个数。两个数组共有m+n个数,若m+n是奇数,那么就是找第(m+n)/2+1个数,偶数的话就是找第(m+n)/2个数和第(m+n)/2+1个数的平均数。转换成找第k个元素之后,在二分查找时就避开了奇数偶数情况不同的问题,只管去第k个元素。

    具体的做法是这样的:

    假设我现在要在数组A和数组B中查找第k个数(假设A的长度m不大于B的长度n,这里k一定不大于m+n)。

    即,当前问题:找A和B的第k个数。(很显然,数列“前x个数的最右数”就是数列的“第x个数”。所以找第x个数和找前x个数是一样的。因为一旦确定了前x个数,这些数的最右数就是第x个数。)

    我先假定最终的前k个数中有一半(k/2)在A中,另一半(k-k/2)在B中。当然,如果A中总个数m少于k/2,我只好假定最终的前k个数中有m个在A中,另外k-m个在B中。

    做好上面的假定之后,根据我的假定,A中的在前k个数中的数的最右数是第pa个数,B中的在前k个数中的数的最右数叫第pb个数。

1、如果第pa数等于第pb数,那么最终的前k个数的最右数一定等于第pa数(是pa还是pb无所谓了),也就是说最终的第k个数就是第pa数。

2、如果第pa数大于第pb数,那么说明,我所假定的B中目前属于前k个数的数确实是属于最终的前k个数,并且后面还有部分数也属于。我所假定的A中目前属于前k个数的数不一定都属于最终的前k个数。好了,于是我就把B中确实是属于最终的前k个数的这pb个数(即第pb和第pb之前的这些数)切掉,变成B'。问题就变为去找A和B'中的前k-pb个数。

3、如果第pa数小于第pb数,那么说明,我所假定的A中目前属于前k个数的数确实是属于最终的前k个数,并且后面还有部分数也属于(有可能后面已经没数了)。我所假定的B中目前属于前k个数的数不一定都属于最终的前k个数。好了,于是我就把A中确实是属于最终的前k个数的这pa个数(即第pa和第pa之前的这些数,有可能就是A中所有的数)切掉,变成了A'(可能是空的)。问题变为去找A'和B中的前k-pa个数。


根据上面的算法,可以看出是在很努力的折半,但是每次折半也只是折半其中某一个数组。之前我的错误想法一直是在同时折半两个数组,所以得不到正确结果。


代码:

class Solution {
public:
int findkth(int A[], int m, int B[], int n, int k)
{
	if(m > n) //这一点很聪明:将对称的情况直接搞定。
		return findkth(B, n, A, m, k);
	if(m == 0)
		return B[k-1];
	if(k == 1)
		return A[0]<B[0]?A[0]:B[0];

	int pa = k/2<m?k/2:m; //假定前k个数有pa个数在A中
	int pb = k - pa; //假定前k个数有k-pa个数在B中
	//这里我努力的折半分配,但如果A中数太少也没办法
	if(A[pa-1] < B[pb-1]) //A切去一半
		return findkth(A+pa, m-pa, B, n, k-pa);
	else if(A[pa-1] > B[pb-1]) //B切去一部分
		return findkth(A,m,B+pb,n-pb,k-pb);
	else
		return A[pa-1];
}

double findMedianSortedArrays(int A[], int m, int B[], int n)
{
	int sum = m + n;
	if(sum%2) //问题转化,将中位数的奇数偶数区别留在了最外面
		return findkth(A,m,B,n,sum/2+1);
	else
		return ((double)findkth(A,m,B,n,sum/2)+findkth(A,m,B,n,sum/2+1))/2;
}
};

总结思考

1、问题转化思想:这里将求中位数的问题转换成了求第k大的数的问题。表面上看似乎复杂化了问题。但实际上对于两个数组的情况,后者更能有效的解决问题。当发现自己解决某个问题时,代码越写越长,过程越捋越乱的时候,一定存在更好的思想没被想到。

2、编码技巧:在算法中,你会假定许多东西,比如两个同等地位的数组,假定一个比另一个长,那么在编码的时候,我完全没有必要将对称的情况重写一边对称的代码,而是应该将参数反过来递归调用一次!


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值