我发现leetcode上的题,都有一个特色,就是初一看,这么简单。再一看,真tm难。算法
我把这道题发群里,很多人以为这道题简单。我只能说你去试着作一下就清楚了。数组
这道题,在难度系数为5
,这是什么概念呢。是LeetCode是难度系数最高的。
ide
这道题,要想作对。函数
须要知道两个知识。spa
什么是中位数code
什么是时间复杂度为O(log(n+m))blog
第一点,什么是中位数。
比方说:
奇位数:[3, 6, 9]的中位数是6
偶位数:[3, 6, 8, 9]的中位数是6+8/2
= 7
排序
第二点:时间复杂度。
什么是时间复杂度,我也很难讲得明白。若是你须要了解更多,你能够去找本算法的书,亦或者网上搜下。我这里只能讲本节须要了解的知识噢。递归
log级别的时间复杂度,老司机都会联想到「二分查找法」。索引
我们举个例子说.
如今让你在一个有序列表
[1,3,6,7,9,13,15,17,23] 里找出17
在哪一个位置上。
使用二分查找法,是先拿这个列表最中的数9
,先和17
进行比较,发现17>9,那再拿9后面的子列表[13,15,17,23]的最中间的数,因为是偶数,我们取中位数16
和17
对比,发现小于16<17,再后面的子列表[17,23]的中位数20
和17
对比,发现17<20,最后只剩下17这一个数了,取出其索引,就知道他的位置了。
上面这个列表长度是9,用时间复杂度来看,就是 log29=3.1699250014
其意思是说,最多须要3次。和咱们上面例子,找出17的位置用了三次(这是最坏的状况下用的次数)是吻合的。
必定要注意的是,使用二分查找法的前提是列表已是有序。这和咱们本道题,并不同
惭愧。这道题三个小时我都没作出来,没有想到一个好的思路。遂放弃。
即便这样,在看别人的答案时,依然有一种醍醐灌顶的感受(前提是你有思考过)。具体的解析,在下面我会来剖析,否则相信有很多人会看不懂。
对于一个长度为n的已排序数列a。
若n为奇数,中位数为a[n / 2 + 1], 若n为偶数,则中位数(a[n / 2] + a[n / 2 + 1]) / 2; 若是咱们能够在两个数列中求出第K小的元素,即可以解决该问题; 不妨设数列A元素个数为n,数列B元素个数为m,各自升序排序,求第k小元素; 取A[k / 2] B[k / 2] 比较; 若是 A[k / 2] > B[k / 2] 那么,所求的元素必然不在B的前k / 2个元素中(证实反证法);
反之,必然不在A的前k / 2个元素中,因而咱们能够将A或B数列的前k / 2元素删去,求剩下两个数列的; k - k / 2小元素,因而获得了数据规模变小的同类问题,递归解决; 若是 k / 2 大于某数列个数,所求元素必然不在另外一数列的前k / 2个元素中,同上操做就好。
若是人读上面的思路以为难以理解,让小明来试着解释一番。
若是是奇数,那么中位数一定在两个列表A和B中的某个元素。
若是是偶数,那么中位数必定是两个元素的平均数。
那么问题,咱们能够将其转化一下。
若是是奇数,就要再两个列表里找到一个第m小
的数。
若是是偶数,就要再两个列表里找到一个第n小
和第n+1小
的数,而后再取平均数。
如今思路比较清晰了,不管是哪一种状况,咱们都得找到第k小
的数。因此咱们得实现一个这样的公共函数。
整个问题的精髓都在这个公共函数里。包括上面的二分查找法的思想,也将在这里面体现。
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
len1, len2 = len(nums1), len(nums2)
if (len1 + len2) % 2 == 1:
# 是奇数
return self.getKth(nums1, nums2, (len1 + len2)//2 + 1)
else:
# 是偶数
return (self.getKth(nums1, nums2, (len1 + len2)//2) +
self.getKth(nums1, nums2, (len1 + len2)//2 + 1)) * 0.5
# 公共函数:查找两个列表里第k小的数
def getKth(self, A, B, k):
m, n = len(A), len(B)
# 保证A比B长度短
if m > n:
return self.getKth(B, A, k)
left, right = 0, m
# 这个while循环,是找A中<=(整个有序数组中第k个数)的最大数
while left < right:
mid = left + (right - left) // 2
# --------------------------------------------
# x = k - 1 - mid:表示在B中前k-mid个的索引
if 0 <= k - 1 - mid < n and A[mid] >= B[k - 1 - mid]:
# 进入这里,代表B中的前k-mid个都比中位数小,被剔除
right = mid
else:
# 进入这里,代表A中的前mid个都比中位数小,被剔除
left = mid + 1
# --------------------------------------------
Ai_minus_1 = A[left - 1] if left - 1 >= 0 else float("-inf")
# 这个是找B中<=(整个有序数组中第k个数)的最大数
Bj = B[(k - left) - 1] if k - 1 - left >= 0 else float("-inf")
return max(Ai_minus_1, Bj)
在看代码的时候,上面两长线包围的代码块,是最难以理解的部分,这里再解释一下。
假设咱们要找两个列表里第k小的数,那么必将有x个在A列表,k-x个在B列表。
如何找出这个x是何值呢,就使用二分查找法,一次一次试探。
先从A列表中找到最中间的数(这边使用//
,向下取整,假设其索引为m
),去和B列表中的第k-m
,对比,若是A[m]
<B[k-m]
,那么说明A的前m个元素都比第k小
的那个数小,能够剔除了。
接下来,到A[m+1:]
的最中间的数(假设其索引为n
)和B[:]
中每(k-m)-n
对比,和上面同样,再剔除一半,直到最后,只剩下一个数。这个数在A中比第k小
的数小的最大数。听起来很绕,请必定去仔细阅读代码。
好啦。剖析就到这里。小明已经尽量把这个解法讲得简单明了。相信比网上大多数的讲解都更加让人容易理解。
在这道题目中,我陷入了一个死胡同,说是求中位数,我就硬抓着中位数不放。到最后也没能想出一个很好的思路。
到最后看了大神们的解答,才知道,有的时候能够将问题转换一个思路,问题就迎刃而解了。