【算法】分治算法案例— 两个排序数组的中位数

两个排序的数组A和B分别含有m和n个数,找到两个排序数组的中位数,要求时间复杂度应为O(log (m+n))。

 

 

样例

给出数组A = [1,2,3,4,5,6] B = [2,3,4,5],中位数3.5

给出数组A = [1,2,3] B = [4,5],中位数 3

挑战 

时间复杂度为O(log n)

 

 

 

这个解法是我在leetcode看到的一个非常好的解法。

解法:

要解决这个问题,我们需要了解“中位数的用途是什么”。在统计中,中位数用于将一个集合划分为两个等长子集,其中一个子集总是大于另一个子集。如果我们理解中位数的用法,我们非常接近答案。


首先,让我们砍一成两个部分在随机位置i:

      left_A             |        right_A
A[0], A[1], ..., A[i-1]  |  A[i], A[i+1], ..., A[m-1]
由于A有m个元素,所以有m + 1种切割(i = 0〜m)。我们知道:len(left_A)= i,len(right_A)= m - i。注意:当i = 0时,left_A是空的,当i = m时,right_A是空的。


以同样的方式,在随机位置j将B切成两部分:


      left_B             |        right_B
B[0], B[1], ..., B[j-1]  |  B[j], B[j+1], ..., B[n-1]
把left_A和left_B放到一个集合中,并把right_A和right_B放到另一个集合中。我们把它们命名为left_part和right_part:


      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]


如果数组总{A,B}长度为偶数:
1) len(left_part) == len(right_part)
2) max(left_part) <= min(right_part)
那么我们把{A,B}中的所有元素分成长度相等的两部分,其中一部分总是大于另一部分。
那么median =(max(left_part)+ min(right_part))/ 2。




为了确保这两个条件,我们只需要确保:
(1) i + j == m - i + n - j (或: m - i + n - j + 1)
    如果 n >= m,我们只需要设置: i = 0 ~ m, j = (m + n + 1)/2 - i
(2) B[j-1] <= A[i] 和 A[i-1] <= B[j]
ps.1为简单起见,即使i = 0 / i = m / j = 0 / j = 1,我假设A [i-1],B [j-1],A [i],B [j] n。最后我会谈谈如何处理这些边缘值。
ps.2为什么n> = m?因为0 <= i <= m且j =(m + n + 1)/ 2-i,所以我必须确定j是非感性的。如果n <m,那么j可能是不好的,那会导致错误的结果。


所以我们需要做的是:


搜索 i in [0, m], 找到一个对象i:
    B[j-1] <= A[i] and A[i-1] <= B[j], ( where j = (m + n + 1)/2 - i )


我们可以按照以下步骤进行二进制搜索:

<1> 设置 imin = 0, imax = m,然后开始搜索 [imin, imax]

<2> 设置 i = (imin + imax)/2, j = (m + n + 1)/2 - i

<3> 现在我有 len(left_part)==len(right_part). 只有3种情况
我们可能会遇到:
    <a> B[j-1] <= A[i] and A[i-1] <= B[j]
       表示我们已经找到了对象i,所以停止搜索。
    <b> B[j-1] > A[i]
        意思是“i”太小了。我们必须“ajust”来得到“B[j - 1]< = A[i]”。
        我们可以“增加” i 吗?
            是的。因为当i增加时,j会减少。
            所以B[j - 1]减少,A[i]增加,B[j - 1]< = A[i]' 能够得到满足。
        我们可以“减少” i 吗?
            “不!因为当我减少时,j会增加。
            所以B[j - 1]增加,A[i]减少,B[j - 1]< = A[i]意图是永远不会满足的。
        所以我们必须“增加”i,也就是说,我们必须只搜索范围到
        [i+1, imax]. 所以,设置 imin = i+1, and goto <2>.
    <c> A[i-1] > B[j]
        意思是A[i - 1]太大了。我们必须“减少”我得到“A[i -1]<= B[j]”。
        也就是说,我们必须搜索范围到 [imin, i-1].
        所以,设置 imax = i-1, and goto <2>.

 

当找到对象i时,中位数是:

max(A[i-1], B[j-1]) (when m + n is odd)
or (max(A[i-1], B[j-1]) + min(A[i], B[j]))/2 (when m + n is even)
现在让我们考虑边缘值i = 0,i = m,j = 0,j = n,其中A [i-1],B [j-1],A [i],B [j]可能不存在。其实这种情况比你想象的要容易得多。

我们需要做的是确保max(left_part) <= min(right_part)。因此,如果i和j不是边的值(意味着A [i-1],B [j-1],A [i],B [j]都存在),那么我们必须检查B [j-1] <= A [i]和A [i-1] <= B [j]。但是如果A [i-1],B [j-1],A [i],B [j]中的一些不存在,那么我们不需要检查这两个条件中的一个(或两个)。例如,如果i = 0,那么A [i-1]不存在,那么我们不需要检查A [i-1] <= B [j]。所以,我们需要做的是:

Searching i in [0, m], to find an object `i` that:
    (j == 0 or i == m or B[j-1] <= A[i]) and
    (i == 0 or j == n or A[i-1] <= B[j])
    where j = (m + n + 1)/2 - i
而在搜索循环中,我们只会遇到三种情况:

<a> (j == 0 or i == m or B[j-1] <= A[i]) and
    (i == 0 or j = n or A[i-1] <= B[j])
    Means i is perfect, we can stop searching.

<b> j > 0 and i < m and B[j - 1] > A[i]
    Means i is too small, we must increase it.

<c> i > 0 and j < n and A[i - 1] > B[j]
    Means i is too big, we must decrease it.

i < m ==> j > 0和i > 0 ==> j < n。因为:

m <= n, i < m ==> j = (m+n+1)/2 - i > (m+n+1)/2 - m >= (2*m+1)/2 - m >= 0    
m <= n, i > 0 ==> j = (m+n+1)/2 - i < (m+n+1)/2 <= (2*n+1)/2 <= n
所以在情况\ <b \>和\ <c \>中,我们不需要检查是否j > 0是j < n。





代码:


 

def median(A, B):
    m, n = len(A), len(B)
    if m > n:
        A, B, m, n = B, A, n, m
    if n == 0:
        raise ValueError


    imin, imax, half_len = 0, m, (m + n + 1) / 2
    while imin <= imax:
        i = (imin + imax) / 2
        j = half_len - i
        if i < m and B[j-1] > A[i]:
            # i太小了,必须增加它
            imin = i + 1
        elif i > 0 and A[i-1] > B[j]:
            # i太大了,必须减少它
            imax = i - 1
        else:
            # i是完美的


            if i == 0: max_of_left = B[j-1]
            elif j == 0: max_of_left = A[i-1]
            else: max_of_left = max(A[i-1], B[j-1])


            if (m + n) % 2 == 1:
                return max_of_left


            if i == m: min_of_right = B[j]
            elif j == n: min_of_right = A[i]
            else: min_of_right = min(A[i], B[j])


            return (max_of_left + min_of_right) / 2.0

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值