Algorithm之路四:Median of Two Sorted Arrays

来源:互联网 发布:常见数据库 编辑:程序博客网 时间:2024/06/10 19:19

题目:

给定两个任意的从小到大排好序的整形数组,现在要求求两个数组整体的中位数,并且要求程序的时间复杂度为O(log(m+n))

举例:

A={1,3,5,7},B={2,4,6},median = 4;

A={1,3},B={2},median = 2;

A={},B={},median = 0;

思路:

直接的想法:

将两个数组合到一起,根据两个数组的长度之和,求出中位数。

这种做法的时间复杂度为O(m+n),不满足题意。

思考后的想法:

题目中要求的时间复杂度为O(log(m+n)),那就应该和二分查询有关,那么利用二分法找到一个数i,使得可以求出和该数有关的其他数据,再根据求得数据得出中位数,这样可能就可以使得时间复杂度达到O(log(m+n))。

分析一下:中位数是指在一串按照某个顺序排好序后的数组中,排在最中间的那个数,如果数组共有偶数个元素,那么中位数为最中间的两个数的平均值。

如果将两个数组A[n]和B[m]合在一起,将其根据中位数分隔开,形成两部分,即left_part和right_part,那么可以得到这样一种情况:

 A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1] B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]
不妨假设left_part比right_part的元素一样多,或者多一个出来,那么中位数可以由A[i-1],B[j-1],A[i],B[j]求出。

为了形成以上的分割,需要满足以下条件:

B[j1] <= A[i] &&A[i-1]<=B[j]

而根据left_part和right_part的元素相等,或者左侧比右侧多一个的前提可以得出i + j == m + n - i - j,或者i + j == m + n + 1 - i - j,那么我们可以直接设置j的值为(m+n+1)/2-i,这样设置的原因如下:

如果m+n为偶数,那么(m+n)/2-i和(m+n+1)/2-i是一样的,如果m+n为奇数,那么(m+n)/2-i要比(m+n+1)/2-i少1,但是这时left_part的部分要比right_part多一个,所以j=(m+n+1)/2-i也是没有问题的。

因为在一个确定的数组中,中位数是确定的,那么i也是确定的,即对于一组A和B涞说,只有唯一一个确定的i可以满足上述条件。于是便可以采用二分查找的方式查找i。

显然i和j的关系是一增一降,所以有以下几种情况:

①A[i-1]>B[j],说明此时的i太大了,需要缩小i;

②B[j-1]>A[i],说明此时的i太小了,需要增大i;

③满足B[j-1] <= A[i] && A[i-1] <= B[j],此时i合适,则计算中位数。

这时会出现这样一个问题,如果说最终得到left_part或者right_part中没有A或B中的元素怎么办,这种情况情况说明出现了i == 0、j == 0、i == n、j == m中的一种,但是此时仍然已经形成了合适的left_part和right_part,那么只需要在处理情况③时稍做处理即可。

代码:

public static double findMedianSortedArrays(int[] A, int[] B) {        int n = A.length,m = B.length;//A,B的长度        if(n == 0 && m != 0)//有一个或者两个数组为空        {        if(m % 2 == 0)        return (double)(B[m/2]+B[m/2+1])/2;        else        return (double)B[m/2];        }        if(m == 0 && n != 0)        {        if(n % 2 == 0)        return (double)(A[n/2]+A[n/2+1])/2;        else        return (double)A[n/2];        }        if(n == 0 && m == 0)        return 0.0;        if(A[n - 1]<=B[0]||A[0]>=B[m - 1])//两个数组的范围没有交叉或刚好交叉,计算的时间为O(1)        {        if(A[0]>=B[m-1])        {         int[] temp = A; A = B; B = temp;                 int tmp = m; m = n; n = tmp;        }        if((m + n)%2 == 0)        {        if(n < (m + n)/2)        return (double)(B[(m + n)/2 - n - 1]+B[(m + n)/2 - n])/2;        else if(n > (m + n)/2)        return (double)(A[(m + n)/2 - 1]+A[( m + n)/2])/2;        else        return (double)(A[n - 1]+B[0])/2;        }        else        {        if(n < (m + n + 1)/2)        return B[(m + n + 1)/2 - n - 1];        else        return A[(m + n + 1)/2 - 1];        }        }        if(n > m)return findMedianSortedArrays(B,A);        //两个数组的范围有超过一个数的交叉        int head = 0,tail = n,i,j,half = (m + n + 1)/2;//用于计算j        while(head <= tail)        {        i = (head + tail)/2;//从A的中部开始找        j = half - i;//对应的j        if(i < tail &&B[j-1] > A[i])//i取小了        head = i + 1;        else if(i > head &&A[i-1] > B[j])//i取大了        tail = i - 1;        else//i取得刚刚好        {        int maxLeft = 0;        if(i == 0)//A在左侧没有元素        maxLeft = B[j-1];        else if(j == 0)//B在左侧没有元素        maxLeft = A[i-1];        else        maxLeft = Math.max(A[i-1], B[j-1]);        if((m + n)%2 == 1)//如果共有奇数个元素        return maxLeft;        int maxRight = 0;        if(i == n)        maxRight = B[j];        else if(j == m)        maxRight = A[i];        else        maxRight = Math.min(A[i], B[j]);        return (double)(maxLeft + maxRight)/2;        }        }        return 0.0;    }

问题:

在进入二分查找前,先判断一下两个数组的情况,如果有一个数组为空,则只需要计算另一个数组的中位数即可。

如果两个数组均不为空,并且一个数组中的元素均大于另一个数组的元素的最大值,那么也只需O(1)的时间内把中位数求出。

但是这样并不能提高时间复杂度,因为只保留二分法查找以内的部分,当出现以上的情况时,也只需要O(1)的时间。。。。。。

时间复杂度:

从以上的分析中,我们已经可以看出,在循环内查找的次数只需要O(log n)的时间,但是这时建立在n<m的情况下,所以整体考虑,时间复杂度为O(log(min(m,n))),此时和题目中要求的时间复杂度表面上看起来有点不同,但实际上是一样数量级的,O(log(min(m,n))) =O(log(0.5(m+n)) = O(log(m+n)) - O(1) = O(log(m+n))。

空间复杂度:

只需要几个临时变量的空间,空间复杂度为O(1)。

原创粉丝点击