LeetCode 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

整体思路

因为题目中对时间复杂度要求是log(m+n)级别的,直接想法是:

递归:大问题转化为小问题。
二分查找:利用数组的有序性。

递归首先需要定义问题,开始想到的是:获取两个升序数组的中间值
对于两数组总体元素为奇数的情况,这种问题定义是满足的。
但是对于偶数的情况,这种问题定义略微复杂。

所以,转化问题定义:获取两个个升序列数组中,整体从大到小排序后,第targetpos个元素

对于奇数总数情况,寻找的是第(m + n)/2 + 1个。
对于偶数总数情况,寻找的是第(m + n)/2和(m + n)/2 +1 个的平均,需要求两次。

1、递归边界

根据递归,首先想到的是问题的边界,也就是不再递归的情况:
1、两个数组中存在空数组或者单一元素数组。
2、两个升序数组,一个数组的最小值大于另外一个最大值。
3、targetpos为1。

2、递归拆解

开始想的是每次拆分一个数组的一半,把某个数组第一个元素拿出来,通过在第二个数组中二分查找,看看在第二个数组中能排第几个。
然后,把小于该元素在第二个数组中的元素都删除,可以进行问题的拆分。

后来想想,这里没用到第一个数组的有序性,而且也没对第一个数组进行问题的拆解,所以继续进行优化:

把第一个数组的中间值取出来,放在第二个数组中,利用二分查找,看看在第二个数组中能排第几个,这样,不论第一个数组中间值大了或者小了,都能每次对两个数组整体进行一半拆分。

3、复杂度分析

因为每次对(m+n)的整体进行拆分,所以整体递归复杂度O(log(m+n))。
二分查找第一个数组中间值在第二个数组中排第几,需要用二分查找,复杂度O(log(n))。
整体复杂度O(log(m+n) + log(n)),其实是满足题目要求的。

Python代码

# coding: utf-8
class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """ :type nums1: List[int] :type nums2: List[int] :rtype: float """
        l1 = len(nums1)
        l2 = len(nums2)

        if (l1 + l2) % 2 == 1:
            target = self.findTargetPosSortedArrays(nums1, nums2, 0, l1 - 1, 0, l2 - 1, (l1 + l2)/2 + 1)
        else:
            x1 = self.findTargetPosSortedArrays(nums1, nums2, 0, l1 - 1, 0, l2 - 1, (l1 + l2)/2)
            x2 = self.findTargetPosSortedArrays(nums1, nums2, 0, l1 - 1, 0, l2 - 1, (l1 + l2)/2 + 1)
            # print "x1:%s, x2:%s" % (x1, x2)
            target = (x1 + x2)/(2.0)
        return target

    def findTargetPosSortedArrays(self, nums1, nums2, nums1begin, nums1end, nums2begin, nums2end, targetpos):
        """ 寻找nums1和nums2中第targetpos个元素 nums1begin 表示 nums1数组开始下标,从0开始 nums1end 表示 nums1数组结束下标,从0开始 nums2begin 表示 nums2数组开始下标,从0开始 nums2end 表示 nums2数组结束下标,从0开始 targetpos表示需要获取nums1和nums2数组整体排序后的第几个,从1开始 """

        nums1len = nums1end - nums1begin + 1
        nums2len = nums2end - nums2begin + 1

        # print "Before, targetpos:%s, nums1begin:%s, nums1end:%s, nums2begin:%s, nums2end:%s" % (targetpos, nums1begin, nums1end, nums2begin, nums2end)
        if nums1len <= 0:
            return nums2[nums2begin + targetpos - 1]

        elif nums2len <= 0:
            return nums1[nums1begin + targetpos - 1]

        if targetpos == 1:
            return min(nums1[nums1begin], nums2[nums2begin])

        # print "%s,%s" % (nums1begin, nums2end) 
        if nums1[nums1begin] >= nums2[nums2end]:
            # 1大2小
            if targetpos > nums2len:
                return nums1[targetpos-nums2len-1 + nums1begin]
            else:
                return nums2[targetpos-1 + nums2begin]

        if nums1[nums1end] <= nums2[nums2begin]:
            # 1小2大
            if targetpos > nums1len:
                return nums2[targetpos-nums1len-1 + nums2begin]
            else:
                return nums1[targetpos-1 + nums1begin]

        if nums1len == 1:
            # nums2one表示nums2中可能的第一个
            nums2one = None
            if (nums2begin + targetpos - 2) >= nums2begin and (nums2begin + targetpos - 2) <= nums2end:
                nums2one = nums2[nums2begin + targetpos - 2]
            nums2two = None 
            if (nums2begin + targetpos - 1) >= nums2begin and (nums2begin + targetpos - 1) <= nums2end:
                nums2two = nums2[nums2begin + targetpos - 1]

            if nums2two and nums2two <= nums1[nums1begin]:
                return nums2two
            elif nums2two is None:
                return max(nums1[nums1begin], nums2one)
            elif nums2one is None:
                return nums1[nums1begin]
            elif nums2one and nums2two and nums2one <= nums1[nums1begin] and nums2two >= nums1[nums1begin]:
                return nums1[nums1begin]
            elif nums2one >= nums1[nums1begin]:
                return nums2one

        if nums2len == 1:
            nums1one = None
            if (nums1begin + targetpos - 2) >= nums1begin and (nums1begin + targetpos - 2) <= nums1end:
                nums1one = nums1[nums1begin + targetpos - 2]
            nums1two = None 
            if (nums1begin + targetpos - 1) >= nums1begin and (nums1begin + targetpos - 1) <= nums1end:
                nums1two = nums1[nums1begin + targetpos - 1]

            if nums1two and nums1two <= nums2[nums2begin]:
                return nums1two
            elif nums1two is None:
                return max(nums2[nums2begin], nums1one)
            elif nums1one is None:
                return nums2[nums2begin]
            elif nums1one and nums1two and nums1one <= nums2[nums2begin] and nums1two >= nums2[nums2begin]:
                return nums2[nums2begin]
            elif nums1one >= nums2[nums2begin]:
                return nums1one

        middleNums1Pos = (nums1begin + nums1end) / 2
        middleNums2Pos = (nums2begin + nums2end) / 2
        middleNums1 = nums1[middleNums1Pos]
        middleNums2 = nums2[middleNums2Pos]

        lessThanNums1MiddleCountInNums2 = self.findLessCount(nums2, nums2begin, nums2end, middleNums1)
        # 在nums2中小于nums1中间点的数量
        lessThanNums1MiddleCountInNums1 = middleNums1Pos - nums1begin
        # 在nums1中小于nums1中间点的数量


        middleNums1PosIn2nums = lessThanNums1MiddleCountInNums2 + lessThanNums1MiddleCountInNums1 + 1
        # middleNums1在两数组中应该的位置,前面包含的数字全小于

        # print "After, targetpos:%s, middleNums1:%s, lessThanNums1MiddleCountInNums1:%s, lessThanNums1MiddleCountInNums2:%s, middleNums1PosIn2nums:%s" % (targetpos, middleNums1, lessThanNums1MiddleCountInNums1, lessThanNums1MiddleCountInNums2, middleNums1PosIn2nums)
        if targetpos == middleNums1PosIn2nums:
            return middleNums1
        elif targetpos > middleNums1PosIn2nums:
            return self.findTargetPosSortedArrays(nums1, nums2, nums1begin + lessThanNums1MiddleCountInNums1 + 1, nums1end, nums2begin + lessThanNums1MiddleCountInNums2, nums2end, targetpos - lessThanNums1MiddleCountInNums2 - lessThanNums1MiddleCountInNums1 - 1)
        elif targetpos < middleNums1PosIn2nums:
            return self.findTargetPosSortedArrays(nums1, nums2, nums1begin, nums1begin + lessThanNums1MiddleCountInNums1 - 1, nums2begin, nums2begin + lessThanNums1MiddleCountInNums2 - 1, targetpos)

    def findLessCount(self, arrays, begin, end, n):
        """ 二分寻找升序数组arrays中小于n的数字count """
        low = begin
        high = end
        while low <= high:
            middle = (low + high) / 2
            if arrays[middle] < n:
                low = middle + 1
            else:
                high = middle - 1
        return (high - begin + 1)

if __name__ == "__main__":
    solution = Solution()
    nums1 = [2,3]
    nums2 = [1,4,5,6]
    print "nums1:%s, nums2:%s" % (nums1, nums2)
    print solution.findMedianSortedArrays(nums1, nums2)



中间问题

1、二分查找,和递归分界这种地方,临界值的+1,-1情况,很容易出bug。
2、问题输入,比如其中一个空数组情况。
3、数组参考点,因为代码中整体没有利用其他空间,只是借助现有数组,进行下标的判定,所以每次递归数组需要进行相对开始下标进行偏移,开始下标第二次后就不是0了。

其他优化

针对python可以返回多个值情况,可以对偶数总数情况做优化,找到第(m + n)/2个,同时找到这个数在nums1和nums2的位置,就可以推算出第(m + n)/2+1个元素了。
也就是说偶数总数的情况,也只要求一个就行。

点赞