Median of Two Sorted Arrays

本文介绍了一种高效算法,用于求解两个已排序数组合并后的中位数问题,复杂度为O(log(m+n))。通过理解中位数概念及巧妙运用二分查找技巧,实现快速准确地找到中位数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现给两个已排序的数组nums1和nums2,长度分别为m和n

求两个数组合并后的中位数,要求复杂度为O( log( m+n ) )


  • 例1:
nums1 = [1, 3] 
nums2 = [2]

The median is 2.0
  • 例2:
 nums1 = [1, 2]
 nums2 = [3, 4]

 The median is (2 + 3)/2 = 2.5

虽然二分的复杂度是O( log(n) ),但合并的复杂度是O( n ),只能找trick咯…


题解

原文链接

为了解决这个问题,首先要理解中位数有什么用
在统计学中,中位数用于:“把一个集合分成两个长度相等的子集,其中一个子集的任何一个元素总是大于另一个子集的任何元素(即一个子集中最小的元素大于另一个子集中最大的元素)”
理解了这个,我们就非常接近问题的答案了。


设集合A、B长度分别为m、n
取随机数i满足0<=i<=m , j满足0<=j<=n

首先我们在i位置把A分成左右两部分:

      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

同样,我们在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放在一起称为left_part,同样把right_A和right_B放在一起称为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合并之后得到的集合{A, B}就被分成了两个子集left_part和right_part
中位数为 ( max(left_part) + min(right_part) )/2

1) len(left_part) == len(right_part)//长度相等
2) max(left_part) <= min(right_part)//大小关系

1)
len( left_part ) == i + j
len( right_part) == m - i + n - j(或 m-i+n-j+1)
2)
A[i-1] <= A[i] 、B[j-1] <= B[j]
B[j-1] <= A[i] 、A[i-1] <= B[j]

为了满足这两个条件,只需要保证:

(1) i + j == m - i + n - j //m+n为偶数
    i + j == m - i + n - j + 1//m+n为奇数,右边+1就把中位数放在了left_part

(2) B[j-1] <= A[i] and A[i-1] <= B[j]
//因为序列已排序,A[i-1] <= A[i] and B[j-1] <= B[j]已成立

注解:
1、暂时只考虑通常情况,边界情况我们之后再讨论
2、为了方便起见,我们保证n >= m,这样就有:i = 0~m ,j = (m + n + 1)/2 - i //否则j可能为负

所以:
在保证n >= m的情况下,我们使i = 0~m , 则j = (m + n + 1)/2 - i ,此时i、j满足条件(1)。
在此前提下我们只需要找到合适的i,使得B[j-1] <= A[i] 、 A[i-1] <= B[j]即可。

下面给出使用二分法搜索i的伪代码:

<1> imin = 0, imax = m 我们在[imin , imax]区间进行搜索
<2> i = (imin + imax)/2, j = (m + n + 1)/2 - i
<3> 此时i、j满足条件(1),而且我们只会遇到以下三种情况:

  • a) B[j-1] <= A[i] and A[i-1] <= B[j]
    • 条件(2)满足,我们找到了合适的i
  • b) B[j-1] > A[i]
    • 此时A[i]较小,需要增大A[i]
    • 由于序列已排序,A[i]与i正相关,所以我们调整搜索区间为[i+1, imax]
    • goto<2>
  • c) A[i-1] > B[j]
    • 同理,我们调整搜索区间为[imin, i-1]
    • goto<2>

注解:
B[j-1] > A[i]和A[i-1] > B[j]不会同时满足,因为A[i] > A[i-1]、B[j] > B[j-1]

此时我们已经找到了合适的i,所以中位数为:
max(A[i-1], B[j-1])//m+n为奇数,设置j的值时我们把它放在了left_part
(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2 //m+n为偶数由于(m + n + 1)/2 == (m+n)/2


好了,是时候讨论边界情况了:
当取 i=0,i=m,j=0,j=n 时 A[i-1],B[j-1],A[i],B[j]可能不存在

事实上边界情况比想象中的容易处理:

当i、j的取值满足条件(1)时,我们还要做的是保证max(left_part) <= min(right_part)
所以:当i、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],只要B[j-1] <= A[i]就保证了max(left_part) <= min(right_part)

由此,我们将合适的i的条件修改为:
(j == 0 or i == m or B[j-1] <= A[i]) and (i == 0 or j == n or A[i-1] <= B[j])


最后,给出代码:

 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 is too small, must increase it
            imin = i + 1
        elif i > 0 and A[i-1] > B[j]:
            # i is too big, must decrease it
            imax = i - 1
        else:
            # i is perfect

            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

完结撒花~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值