我發現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小
的數小的最大數。聽起來很繞,請必定去仔細閱讀代碼。
好啦。剖析就到這裏。小明已經儘量把這個解法講得簡單明瞭。相信比網上大多數的講解都更加讓人容易理解。
在這道題目中,我陷入了一個死衚衕,說是求中位數,我就硬抓着中位數不放。到最後也沒能想出一個很好的思路。
到最後看了大神們的解答,才知道,有的時候能夠將問題轉換一個思路,問題就迎刃而解了。