题:
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)).
算法:
一个解法是把逐个比较两个列表中的数字然后按照顺序合并,取合并后的数组的中位数。复杂度为O(m+n)。
在油管上学习了一种利用Binary Search来解题的方法1,很有趣。O(log(min(m, n)),跑出来在92%左右。基本思路如下图:
由于两个数组已经是有序的了,则若可以把每个数组分成两部分,使得每一个左半部分都小于所有的右半部分,并且左半部分的总个数等于右半部分的总个数(或者相差一个,这个在后面会有说明),那么中位数就可以在分割的位置中得到了。而在寻找这两个合适的分割点的过程中,只需要关心一个数组分割点的左部最大值
X
l
e
f
t
X_{left}
Xleft,
X
r
i
g
h
t
X_{right}
Xright是否小于另一个数组分割点的右部最小值
Y
r
i
g
h
t
Y_{right}
Yright,
Y
r
i
g
h
t
Y_{right}
Yright,如果不满足则要往相应的方向移动,提升算法速度的一大关键在于移动采用的是二分法(i.e. end = xright - 1
or start = xleft + 1
)。
实现:
class Solution:
def findMedianSortedArrays(self, nums1, nums2):
if len(nums1) > len(nums2):
t = nums1
nums1 = nums2
nums2 = t
len_x = len(nums1)
len_y = len(nums2)
len_ttl = len_x + len_y
start_x = 0
end_x = len_x ## don't need to -1
# start_y = 0
# end_y = len_y
# for i in range(3):
while start_x <= end_x: # it's okay to have = since the if below can take care of the = case
print("endx", end_x)
a = (start_x + end_x) // 2 ## partion of x
print("a", a)
b = (len_ttl + 1) // 2 - a ## corresponding partition of y
if len_x == 0:
xleft = -float('Inf')
xright = float('Inf')
elif a == 0:
xleft = -float('Inf')
xright = nums1[a]
elif a == len_x:
xright = float('Inf')
xleft = nums1[a - 1]
else:
xleft = nums1[a - 1]
xright = nums1[a]
print('b',b)
if len_y == 0:
yleft = -float('Inf')
yright = float('Inf')
elif b == 0:
yleft = -float('Inf')
yright = nums2[b]
elif b == len_y:
yright = float('Inf')
yleft = nums2[b - 1]
else:
yleft = nums2[b - 1]
yright = nums2[b]
print(xleft, xright, yleft, yright)
if (xleft <= yright) & (yleft <= xright):
# print(xleft, xright, yleft, yright)
if len_ttl % 2 == 0:
median = (max(xleft, yleft) + min(xright, yright)) / 2
return median
else:
median = max(xleft, yleft)
return median
elif xleft > yright:
end_x = a - 1 ## -1 to exclude the previous xleft. xleft is a num instead of an index!
else: # xright < yleft
start_x = a + 1
虽然算法的理解不难,但实现起来还是费了一番功夫。主要耗费的时间在于界定边界和极端情况。几个要关注的点:
- 两个分割点之和 a + b = l e n g t h 1 + l e n g t h 2 2 a+b=\frac{length_1+length_2}{2} a+b=2length1+length2这能使当合并数组长度为基数的时候,中位数总在分割点的左边出现。
- 始终让数组x代表较短的数组,否则算数组y的分割点b的时候容易超出数组范围。
- 二分法中的起始点的确定
end = xright - 1
orstart = xleft + 1
,暂时还没有想明白为什么,但只用end = xright
会没办法应对某些特殊情况。 - 前半部分的一连串if else想了挺久的,暂时归纳不出好的方法论,只能常看常新了。
思考:
- 要利用好两个数组以排序这个点,即认真审题,利用好每一个条件。
- 边界的划分能力还需要提高,代码实现很大的一个关键在于对边界和极端情况的界定。
- 极端情况可以从算法中需要的几个值不存在的情况入手,比如这里由于要知道分割点左右两边的数字,则要考虑左右没有数字的情况(分割点在最左边,分割点在最右边,空数组等)。
- 多写: )