LeetCode题解——两个有序数组的中值


4.Median of Two Sorted Arrays

描述

有两个排序的数组 nums1nums2,它们的大小分别是 mn

找到两个排序数组的中值。程序的时间复杂度为 O(log(m+n))

你可以认为 nums1nums2 不会同时为空。

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

方法

为了解决这个问题,我们需要明白“中值可以用来做什么”。在统计学里,中值被用来:

将一个集合划分成两个等长的子集,其中一个子集的元素总是比另一个大。

如果我们理解了划分中值的意义,我们就可以很好地理解答案了。

首先将 nums1 在随机位置 i 分成两部分:

			left_nums1       		|          right_nums1

nums1[0], nums1[1], ..., nums1[i-1] | nums1[i], nums1[i+1], ..., nums1[m-1]

因为 nums1m 个元素,所以有 m+1 种划分法(i = 0~m)。

而且我们知道:

len(left_nums1) = i, len(right_nums1) = m - i.

Note: when i = 0, left_nums1 is empty, and when i = m, right_A is empty.

同样的,将 nums2 在随机位置 j 分成两部分:

	left_nums2							|		right_nums2
nums2[0], nums2[1], ..., nums2[j-1]		|	nums2[j], nums2[j+1], ..., nums2[n-1]

left_nums1left_nums2 放在一个集合并将 right_nums1right_nums1放在另一个集合。让我们命名为 left_partright_part

	left_part							|		right_part
nums1[0], nums1[1], ..., nums1[i-1]		|	nums1[i], nums1[i+1], ..., nums2[n-1]
nums2[0], nums2[1], ..., nums2[j-1]		|	nums2[j], nums2[j+1], ..., nums2[n-1]

我们可以确定:

1.len(left_part) = len(right_part)

2.max(left_part) <= min(right_part)

之后我们划分所有在 {nums1, nums2} 的元素到两个等长的集合,其中一个集合的元素总是比另一个大。

m e d i a n = m a x ( l e f t p a r t ) + m i n ( r i g h t p a r t ) 2 median = \frac{max(left_part)+min(right_part)}{2} median=2max(leftpart)+min(rightpart)

为了确保这两个条件,我们需要保证:

1.i+j = m - i + n - j (or: m - i + n - j + 1)

if n >= m, we just need to set: i = 0~m, j = (m+n+1)/2 -i

2.nums2[j-1] <= nums1[i] and nums1[i-1] <= nums2[j]

ps.1 为了简化,我假设 nums1[i-1], nums2[j-1], nums1[i], nums2[j] 总是合法的,即使出现 i=0, i=m, j=0, j=m 的情况。我将会在最后讨论边界情况。

ps.2 为什么需要 n>=m ?因为我必须确保 j 为非负数,因为 0<=i<=m, j=(m+n+1)/2。如果 n < m,j 会是负数,导致错误的结果。

在 [0,m]中搜索 i,找到满足以下条件的 i:

​ nums2[j-1] <= nums1[i] and nums1[i-1] <= nums2[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)。我们可能会遇到下列三种情况:

  • nums2[j-1] <= nums1[i] and nums1[i-1] <= nums2[j]

    表明我们已经找到了满足条件的 i ,停止搜索。

  • nums2[j-1] > nums1[i]

    表明nums1[i] 太小了。我们需要修改 i 来使 nums2[j-1] <= nums1[i]。我们只能通过增加 i 来达到目的,因为减少 i 会使得 j 增大,导致 nums1[i] 更小。调整搜索范围到[i+1, imax],即令 imin = i + 1,跳转到 2.

  • nums1[i-1]>nums2[j]

    表明 nums1[i-1] 太大了。我们必须减少 i 来使 nums1[i-1] <= nums2[j]。调整搜索范围到 [imin, i-1],即令 imax = i - 1,跳转到2.

当满足条件的 i 被找到后,中值即为:

max(nums1[i-1], nums2[j-1]), when m+n is odd

max(nums1[i-1], nums2[j-1]+min(nums1[i], nums2[j]))/2, when m+n is even

现在我们可以考虑边界情况了:i=0, i=m, j=0, j=m。这种情况比你想象中的要好处理。

我们需要做的就是确保 max(left_part) <= min(right_part)。所以,如果 i 和 j 不是边界值,我们必须同时检查nums2[j-1] <= nums1[i] 和 nums1[i-1] <= nums2[j]。但是如果 nums1[i-1], nums2[j-1], nums1[i], nums2[j] 中的一些值不存在,我们就不必检查这两种情况。例如,如果 i = 0, 那么 nums1[i-1] 不存在,我们就不需要检查 nums1[i-1] <= nums2[j]。所以,我们需要做的是:

searching i in [0,m], to find an object i such that:
(j = 0 or i = m or nums2[j-1] <= nums1[i]) and
(i = 0 or j = n or nums1[i-1] <= nums2[j], where j = (m+n+1)/2 -i)

所以我们只需要考虑以下三种情况:

1.(j=0 or i=m or nums2[j-1]<=nums1[i]) and
  (i=0 or j=n or nums1[i-1]<=nums2[j])
  Means i is perfect, we can stop searching
2.j>0 and i nums1[i]
  Means i is too small, we must increase it
3.i>0 and j nums2[j]
  Means i is too big, we must decrease it

Python代码如下:

class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        m, n = len(nums1), len(nums2)
        if m > n:
            m, n, nums1, nums2 = n, m, nums2, nums1
        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 > 0 and nums1[i-1] > nums2[j]:
                imax = i - 1
            elif i < m and nums2[j-1] > nums1[i]:
                imin = 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) % 2 == 1:
                    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 (min_right+max_left) / 2.0
        
        

复杂度分析

  • 时间复杂度:O(log(min(m, n)))。

首先,搜索范围是 [0, m]。搜索的长度将会在每次循环后降低一半。所以我们仅仅需要 log(m) 次循环。因为我们在每次循环中做类似的操作,所以时间复杂度是 O(log(m))。而 m <= n,所以时间复杂度是 O(log(min(m, n)))。

  • 空间复杂度:O(1)。

我们仅仅需要常数级的内存来存储9个局部变量,所以空间复杂度是 O(1)。

你可能感兴趣的:(Algorithms)