题目描述
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例
示例1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3) / 2 = 2.5
求解思路
【注】思路主要是给本人自己看的,以便忘了能记起。
首先,我们需要理解 “中位数的作用是什么”。在统计中,中位数被用来:
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
如果是两个等长的数组,那么我们可以分别求2个数组的中位数,比较大小后,各自删除一半,反复如此,直到两边数组都只剩一个元素为止,最终中位数就是这两个元素的平均数。
然而,此题没要要求两个数组长度必须一致,难就难在这里,所以只能模拟枚举。整体的中位数把元素分割成
l
e
f
t
\bf \blue {left}
left和
r
i
g
h
t
\bf \blue{right}
right两个集合,我们就要探测在这两个集合中nums1和nums2数组各有多少个元素在里面。
简单的方法是类似合并两个有序列表的算法,时间复杂度是
O
(
m
i
n
(
m
,
n
)
)
O(min(m,n))
O(min(m,n)),但是题目要求的是
O
(
l
o
g
(
m
+
n
)
)
O(log(m+n))
O(log(m+n)),显然是要用到递归,不然时间复杂度不可能出现对数。
怎么用递归呢,参考两个数组长度相等找中位数的算法,每次删去一部分,直到最后剩下结果。现在数组长度不相等了,这种二分元素显然行不通,所以退而求其次,二分索引,并且是长度较小的数组的索引。为什么是较短的数组?在后面编码时你会发现,探测长数组的索引可能会导致一些麻烦,需要额外的工作。
那么,二分所引怎么用呢?通常情况,是设置两个哨兵
i
_
m
a
x
i\_max
i_max和
i
_
m
i
n
i\_min
i_min,探测位置
i
=
f
u
n
c
(
i
_
m
i
n
,
i
_
m
a
x
)
i=func(i\_min,i\_max)
i=func(i_min,i_max),通过判断
i
i
i是偏大还是偏小,来调整
i
_
m
a
x
i\_max
i_max或
i
_
m
i
n
i\_min
i_min值,然后继续这一步,直到找到满足中位数要求的
i
i
i。
-------------------------------------下面开始具体算法-------------------------------------
首先,根据数组长度进行排序,令短的为nums1,长的为nums2。m = len(nums1), n = len(nums2)
我们将nums1分成两个部分,左边
i
i
i个元素,右边
m
−
i
m-i
m−i个元素,其中
0
≤
i
≤
m
0\le i \le m
0≤i≤m:
l e f t _ o f _ n u m s 1 \bf{left\_of\_nums1} left_of_nums1 | r i g h t _ o f _ n u m s 1 \bf{right\_of\_nums1} right_of_nums1 | |
---|---|---|
i = 0 i=0 i=0 | None | nums1 |
0 < i < m 0<i<m 0<i<m | nums1[0],nums1[1],…,nums1[i-1] | nums1[i],nums1[i+1],…,nums2[m-1] |
i = m i = m i=m | nums1 | None |
同理,将nums2分成两个部分,左边 j j j个元素,右边 n − i n-i n−i个元素:
l e f t _ o f _ n u m s 2 \bf{left\_of\_nums2} left_of_nums2 | r i g h t _ o f _ n u m s 2 \bf{right\_of\_nums2} right_of_nums2 | |
---|---|---|
j = 0 j=0 j=0 | None | nums2 |
0 < j < m 0<j<m 0<j<m | nums2[0],nums2[1],…,nums2[j-1] | nums2[j],nums2[j+1],…,nums2[n-1] |
j = m j = m j=m | nums2 | None |
将
l
e
f
t
_
o
f
_
n
u
m
s
1
\bf{left\_of\_nums1}
left_of_nums1和
l
e
f
t
_
o
f
_
n
u
m
s
2
\bf{left\_of\_nums2}
left_of_nums2放入集合
l
e
f
t
\bf \blue {left}
left,
r
i
g
h
t
_
o
f
_
n
u
m
s
1
\bf{right\_of\_nums1}
right_of_nums1和
r
i
g
h
t
_
o
f
_
n
u
m
s
2
\bf{right\_of\_nums2}
right_of_nums2放入集合
r
i
g
h
t
\bf \blue {right}
right,如果我们可以确认:
①
l
e
n
(
l
e
f
t
)
=
l
e
n
(
r
i
g
h
t
)
或
者
l
e
n
(
l
e
f
t
)
=
l
e
n
(
r
i
g
h
t
)
+
1
len(\blue {left}) = len( \blue {right}) \ 或者len(\blue {left})\ =\ \ len( \blue {right})+1
len(left)=len(right) 或者len(left) = len(right)+1
②
m
a
x
(
l
e
f
t
)
≤
m
i
n
(
r
i
g
h
t
)
max(\blue {left}) \le min( \blue {right})
max(left)≤min(right)
那么,我们已经将 nums1和nums2中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
那么:
m
e
d
i
a
n
=
m
a
x
_
o
f
_
l
e
f
t
+
m
i
n
_
o
f
_
r
i
g
h
t
2
\bf \blue {median} = \frac{\blue {max\_of\_left} + \blue {min\_of\_right}}{2}
median=2max_of_left+min_of_right
要确保上面两个条件,我们只需保证:
①
c
o
u
n
t
_
o
f
_
l
e
f
t
=
l
e
n
(
l
e
f
t
)
=
⌈
m
+
n
2
⌉
\bf \blue {count\_of\_left} = len(\blue {left}) = \lceil \frac{m\ +\ n}{2}\rceil
count_of_left=len(left)=⌈2m + n⌉
②
i
+
j
=
c
o
u
n
t
_
o
f
_
l
e
f
t
i\ +\ j\ =\ \bf \blue {count\_of\_left}
i + j = count_of_left
③
m
a
x
(
max(
max(nums1[i-1]
,nums2[j-1]
)
≤
m
i
n
(
) \le min(
)≤min(nums[i]
,nums2[j]
)
)
)
【注】边界情况为简单情况,单独讨论即可,上面为一般情况下的做法
结合之前所说,可以这么做:
把上面要满足的条件②放到else语句里,这样,可以把特殊情况的解答也放在else里,减少代码冗余;而if语句用来调整
i
_
m
i
n
i\_min
i_min和
i
_
m
a
x
i\_max
i_max的值.
Python3解答
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m, n = len(nums1), len(nums2)
# 这里为了保证nums1一定是长度较小的数组
if m > n:
nums1, nums2, m, n = nums2, nums1, n, m
# 题目给定数组不会同时为空,也就是m^2+n^2≠0,由于m≤n,故只要n≠0即可
if not n:
raise ValueError("数组长度不同时为零")
i_min, i_max = 0, m
# left集合元素数量,如果m+n是奇数,left比right多一个数据
count_of_left = (m + n + 1) // 2
while i_min <= i_max:
i = (i_min + i_max) // 2 # left有i个nums1的元素
j = count_of_left - i # left有j个nums2的元素
if i > 0 and nums1[i - 1] > nums2[j]:
i_max = i - 1 # i太大,要减少
elif i < m and nums1[i] < nums2[j - 1]:
i_min = i + 1 # i太小,要增加
else:
if i == 0:
max_of_left = nums2[j - 1]
elif j == 0:
max_of_left = nums1[i - 1]
else:
max_of_left = max(nums1[i - 1], nums2[j - 1])
if (m + n) % 2:
return float(max_of_left) # 结果是浮点数
if i == m:
min_of_right = nums2[j]
elif j == n:
min_of_right = nums1[i]
else:
min_of_right = min(nums1[i], nums2[j])
return (max_of_left + min_of_right) / 2.0 # 结果为浮点数