有两个排序的数组 nums1 和nums2,它们的大小分别是 m 和 n。
找到两个排序数组的中值。程序的时间复杂度为 O(log(m+n))。
你可以认为 nums1和 nums2 不会同时为空。
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]
因为 nums1 有 m 个元素,所以有 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_nums1和 left_nums2 放在一个集合并将 right_nums1和 right_nums1放在另一个集合。让我们命名为 left_part 和 right_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
首先,搜索范围是 [0, m]。搜索的长度将会在每次循环后降低一半。所以我们仅仅需要 log(m) 次循环。因为我们在每次循环中做类似的操作,所以时间复杂度是 O(log(m))。而 m <= n,所以时间复杂度是 O(log(min(m, n)))。
我们仅仅需要常数级的内存来存储9个局部变量,所以空间复杂度是 O(1)。