这道题是我最初刷的那20多道之一,但一直没有过,被各种各样的情况折磨死了,直到把所有其他的题都写完,回来看大神对这道题是怎么处理的时候,才惊叹算法的奇妙。再次验证了我的想法,如果要处理各种各样的特殊情况,一定是算法本身有问题。
之前看过很多有关在两个排序数组中找中位数的解法,大多根据两个数组长度不同分了很多种情况,各种讨论。下面要介绍的方法并没有直接求中位数,而是把求中位数转换成了求两个数组合并之后的第(m+n)/2个数(当然根据总数的奇偶有所不同,不过思路上是没有问题的)。用k来表示进入递归的时候,应该求的两个数列合并之后的那个数,用二分的思想,我们先看A[k/2]与B[k/2]的大小关系,如果A[k/2-1]<B[k/2-1],那么A[0,k/2-1]中有没有可能存在合并之后的第K个数呢?A[k/2-1]大于A中在它之前的k/2-1个数,最好的情况,它也大于B中的在B[k/2-1]之前的k/2-1个数,也就是A[k/2-1]最大在合并后的数列中可以排到第(k/2-1+k/2-1+1)个位置,也即第k-1个位置。因此A[k/2-1]这一部分肯定不包含结果,应该直接扔掉。
对称的,当A[k/2-1]>B[k/2-1]时,有类似的结果。
当A[k/2-1] == B[k/2-1]时呢?说明这个数找到了,返回即可。
在实现的时候有几个技巧,两个数列个数不一样,为了下面编码部分的简便,可以在开头判断一下,如果前面数组个数多,就互换一下。第二点,数组中剩余的的元素可能比k/2要少,这时候就取一个较小的值即可。至于最开始两个数组合并后元素个数奇偶的问题,正是由于我们求的是第k个数而不是中位数,大大简化 了。设总数为t,如果是奇数,就求第t/2+1的那个数,如果是偶数,就分别求第t/2和第t/2+1的数,然后求平均。
28行代码完成了我100多行没有完成的事情,算法太美。
class Solution {
public:
double 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 min(A[0], B[0]);
int pa = min(m, k/2), pb = k-pa;
if(A[pa-1]<B[pb-1]){
return findKth(A+pa, m-pa, B, n, k-pa);
}else if(A[pa-1]>B[pb-1]){
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 t = m+n;
if(t&0x1){
return findKth(A, m, B, n, t/2+1);
}else{
return (findKth(A, m, B, n, t/2)+findKth(A, m, B, n, t/2+1))/2;
}
}
};