Leetcode题目分析:Median of Two Sorted Arrays

4. Median of Two Sorted Arrays

题目

There are two sorted arrays nums1 and nums2 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)).

Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

分析

非常经典的一道题了,由于题目要求O(log (m+n))的复杂度,因此必须要使用二分查找。难点在于该如何二分两个列表以及边界条件的处理。

其实在网上关于这道题的讲解有很多,但是对不熟悉分治的人来说还是比较抽象,难以理解。这里我先推荐两个讲解,然后再说说我的一些理解。
视频讲解:https://www.youtube.com/watch?v=LPFhl65R7ww
leetcode讲解:https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2481

基本思路

想象一个虚拟的有序列表C,由列表A、B合并而来。如果一个分割点p将列表C划分成长度相等的两部分,那就能马上得到列表C的中值了。
当然,题目要求时间复杂度为O(log (m+n)),我们不能真正的合并两个列表,因此将有两个分割点p1、p2分别分割列表A和列表B,这两个分割点代表了将虚拟列表C划分成长度相等的两部分的分割点p。我们的目标就是找到它们。

A[0] … A[p1-1] | A[p1] … A[m-1]
B[0] … B[p2-1] | B[p2] … B[n-1]
C[0] … C[p-1] | C[p] … C[m+n-1]

  • 首先要确保p1 + p2确实是列表C的中位分割点
  • 由于列表A、B有序,那么只要A[p1-1] < B[p2] 且 B[p2-1] < A[p1],就能说明划分左边的元素全部小于右边的元素,对应上了列表C的有序性。我们就找到了目标的分割点,可以得到中值了。
  • 如果A[p1-1] > B[p2],说明A划分左边的元素太多了,A划分要向左移动
  • 如果B[p2-1] > A[p1],说明B划分左边的元素太多了,B划分要向左移动

分割点的选择

要确保p1 + p2确实是列表C的中位分割点,在视频中的讲解是这样做的:p1 + p2 = (m + n + 1) // 2
将p1 + p2看作列表C的分割点p,则:
- 分割点左边的元素:C[p-1] = max(A[p1-1], B[p2-1])
- 分割点右边的元素:C[p] = min(A[p1], B[p2])

为什么是m + n + 1?这个+1到底是什么鬼?

在视频中,作者只是轻描淡写的说了一下“采用m + n + 1是因为它对奇偶的处理都很好”(可能作者认为这个简单的东西没必要讲),但我认为完全理解m + n和m + n + 1的区别是很有必要的。(也许是我智商比较低,这个东西还要仔细思考…?

若m + n为偶数,显然(m + n + 1) // 2(m + n) // 2的值是一样的,没有影响。
- 中值 = (C[p-1] + C[p]) / 2
- 例子:若m + n = 4,则p1 + p2 = 2,分割点为2,中值为(C[1] + C[2]) / 2

若m + n为奇数,那么(m + n + 1) // 2(m + n) // 2多了1,也就是C的左边会多出一个元素。此时中值应为左边的这个多出的元素。如果采用p1 + p2 = (m + n) // 2,那么C的右边会多出一个元素,此时中值应为右边的这个元素。
- 采用m + n + 1时:中值 = C[p-1]
- 例子:若m + n = 3,则p1 + p2 = 2,分割点为2,中值为C[2-1] = C[1]
- 采用m + n时:中值 = C[p]
- 例子:若m + n = 3,则p1 + p2 = 1,分割点为1,中值为C[1]

可见,+1或不+1其实都是正确的,并不是一定要采用m + n + 1才正确!事实上两种方式我都AC了。在leetcode的discuss上看到几乎所有人的代码都是采用m + n + 1,感觉有点神奇……

p1、p2的具体选择

视频里已经讲的很详细了,就是保证m <= n,然后定义low = 0, high = m,令p1 = (low + high) // 2
通过移动low与high来移动分割点p1,进而p2因为p1的移动也跟着移动。这样处理的好处是使得时间复杂度变成了O(log (min(m, n)))

边界条件的处理

同样,视频里已经讲的很详细了。不过视频里的代码是用java写的,用到了INT_MIN和INT_MAX这两个常数。尽管这种把某个数看作无限的方法也是很常用的,但总有一点取巧的感觉。因为实际上并不存在一个无限小或无限大的常数(python就没有,因为数字超出int的范围后会转换成大整数)

上文给出的leetcode讲解是用python写的,这个作者就没有用到INT_MIN和INT_MAX,因此有一点是我要特别说明的:这个m + n为奇数时return maxLeft的语句为什么放在求maxLeft与minRight语句的中间?若是先将maxLeft和minRight都求出来再判断m + n的奇偶性并返回结果行不行?
其实本来是可行的,采用这种诡异的顺序只是为了应对一种情况:列表A为空,列表B只有一个元素。此时m = 0,n = 1,p1 = 0,p2 = 1。若将return maxLeft语句放在后面,则p1 == 0和p1 == m时的语句都要执行,minRight = nums2[p2]就会导致超出范围了。

代码

def findMedianSortedArrays(self, nums1, nums2):
    m, n = len(nums1), len(nums2)
    if m > n: return self.findMedianSortedArrays(nums2, nums1)
    low, high = 0, m
    while low <= high:
        #p1, p2 are the partitions of nums1, nums2
        #max left of nums = nums[p-1]; min right of nums = nums[p]
        p1 = (low + high) // 2
        p2 = (m + n + 1) // 2 - p1
        if p1 > 0 and nums1[p1-1] > nums2[p2]:
            #left side of nums1 is too long, decrease p1 (increase p2)
            high = p1 - 1
        elif p1 < m and nums1[p1] < nums2[p2-1]:
            #left side of nums2 is too long, increase p1 (decrease p2)
            low = p1 + 1
        else:
            #this condition means we find the aimed partition
            if p1 == 0: #there is no element on the left side of nums1
                maxLeft = nums2[p2-1]
            elif p2 == 0: #there is no element on the left side of nums2
                maxLeft = nums1[p1-1]
            else:
                maxLeft = max(nums1[p1-1], nums2[p2-1])

            if (m + n) % 2 == 1:
                return maxLeft #total length is odd

            if p1 == m: #there is no element on the right side of nums1
                minRight = nums2[p2]
            elif p2 == n: #there is no element on the right side of nums2
                minRight = nums1[p1]
            else:
                minRight = min(nums1[p1], nums2[p2])

            return (maxLeft + minRight) / 2.0 #total length is even
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值