夏夜之梦的博客

一只博客小白

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:

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

分析

题目的输入为:两个排序的数组nums1,nums2

输出为:nums1,nums2的中位数

本题的难点在于整体的时间复杂度为O(log(m+n))。如果首先将数组nums1,nums2合并之后进行排序,则显然时间复杂度上不满足题目要求。如何利用好nums1,nums2是排序好的数组,是本题的关键。本题同时要处理一定的边际条件,本解答借鉴了LeetCode参考解答。但由于原解答是英文,下面将其翻译为中文。

解答

为了解决该问题,我们首先需要理解中位数的作用。在统计学上,中位数是指

将一个集合等分成两部分,其中一个子集总大于另一个子集

如果我们理解了中位数的作用,我们离答案已经很近了。

首先我们将A在位置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=0m

同时我们知道:

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]

如果我们能保证:

len(left_part) = len(right_part), max(left_part) min(right_part)

这样的话,我们便将A,B划分成等长的两部分,而且右半部分恒大于左半部分。因此

median=max(left_part)+min(right_part)2

为了保证以上两个条件,我们只需要满足:

  1. i+j=mi+nj(或者:mi+nj+1),如果nm,我们可以令i=0m,j=m+n+12i
  2. B[j1]A[i] 同时 A[i1]B[j]

为简单起见,首先假设B[j1],A[i],A[i1],B[j]即使对于j=0,i=m,i=0,j=n也得存在。最后我们将讨论如果处理这些边界条件。

nm的条件是为了保证j是非负数,如果j是负数,则会结果出错。

因此,我们需要做的就是:

[0,m]之间搜寻i,使得 B[j1]A[i] 同时 A[i1]B[j],同时满足j=m+n+12i

因此我们可以采用二分搜索的方式进行:

  1. 设置imin=0,imax=m,在[imin, imax]之间开始查找
  2. i=imin+imax2,j=m+n+12i
  3. 现在我们可以保证len(left_part) = len(right_part),接下来只需要考虑三种情况:
    • B[j1]A[i] 同时 A[i1]B[j],这也意味着我们找到了i,停止搜索
    • B[j1]>A[i],意味着A[i]太小了,因此我们需要增加i(读者可以自己思考)。因此设置imin=i+1,回到第二步。
    • A[i1]>B[j],意味着A[i1]太大了,因此我们需要减小i,因此设置imax=i1,返回第二步。

当找到i之后,中位数为:

max(A[i1],B[j1]),当m+n为奇数时; max(A[i1],B[j1])+min(A[i],B[j])2,当m+n为偶数时

接下来我们考虑对于j=0,i=m,i=0,j=nB[j1],A[i],A[i1],B[j]不存在时的情况。由于我们需要做的是保证max(left_part) min(right_part),因此当i,j不是边界值时,我们需要同时检查B[j1]A[i]A[i1]B[j]。当B[j1],A[i],A[i1],B[j]中的某些值不存在时,我们就可以不用检查上述两个条件中的一个或者两个。举例来说,当i=0时,此时A[i1]不存在,我们就不需要检查A[i1]B[j]。因此我们需要做的是:

[0,m]之间搜寻i,使得:(j=0 或者 i=m 或者 B[j1]A[i]) 同时 (i=0 或者 j=n 或者 A[i1]B[j]

在一个循环过程中,我们会遇到以下三种情况:

  1. j=0 或者 i=m 或者 B[j1]A[i]) 同时 (i=0 或者 j=n 或者 A[i1]B[j]),此时意味着i已经找到,停止搜索。
  2. i<m,同时B[j1]>A[i],增大i
  3. i>0,同时A[i1]>B[j],减小i

程序如下:

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        m, n = len(nums1), len(nums2)
        if m > n:
            nums1, nums2, m, n = nums2, nums1, n, m

        if n == 0:
            return('invalid input')

        i_min, i_max = 0, m
        half_len = int((m+n+1)/2)

        while i_min <= i_max:
            i = int((i_min+i_max)/2)
            j = half_len - i
            if i < m and nums2[j-1] > nums1[i]:
                i_min = i + 1
            elif i > 0 and nums1[i-1] > nums2[j]:
                i_max = i - 1
            else:
                if i == 0: 
                    max_left = nums2[j-1]
                elif j == 0:
                    max_left = nums1[i-1]
                else:
                    max_left = max(nums1[i-1], nums2[j-1])

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

                if i == m:
                    min_right = nums2[j]
                elif j == n:
                    min_right = nums1[i]
                else:
                    min_right = min(nums1[i], nums2[j])

                return (max_left + min_right) / 2.0

        return 0

时间复杂度为O(log(min(m,n))),由于采用二分搜索的方式,因此每迭代一次,搜索长度减小一半。而每一次迭代,时间复杂度为常数,因此整体时间复杂度为O(log(m))。由于mn,因此总的时间复杂度为O(log(min(m,n)))

阅读更多
个人分类: LeetCode
上一篇LeetCode练习3:Longest Substring Without Repeating Characters
下一篇Python 3 编程BUG总结【不断更新】
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭