Leetcode 4. Median of Two Sorted Arrays 尋找兩個有序陣列的中位數
Leetcode 4. Median of Two Sorted Arrays 尋找兩個有序陣列的中位數
標籤: Leetcode
題目地址:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
題目描述
給定兩個大小為 m 和 n 的有序陣列 nums1 和 nums2。
請你找出這兩個有序陣列的中位數,並且要求演算法的時間複雜度為
。
你可以假設 nums1 和 nums2 不會同時為空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
則中位數是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
則中位數是 (2 + 3)/2 = 2.5
題目解釋
這個題目有一種變形可以解釋成求Topk問題,比如給兩個有序陣列,給出一種演算法求出第k小元素,對於中位數問題相當於求第(m+n)/2個元素,這道題的難點不在於題目,而在於其複雜度,我想 的演算法還是比較容易想的到的,直接把兩個有序表合併,然後無論是求Top幾我們都能輕鬆的求出來,但是這樣的話和題目要求不符,因為題目要求 。
這裡給出一種中心擴散法,我沒有計算其複雜度是多少,但是提交上去其耗時為68 ms並且也被AC了,除了這種方法還有一種叫做manacher(馬拉車)的演算法,有興趣的話可以自行了解。
演算法思想
中心擴散法
對於兩個有序陣列要取出來topk,肯定是在第一個數組裡面取幾個,第二個數組裡面也取出來幾個,如果二者取出來的和剛好是k,就找到了topk的第k個。
那麼我們可以假定在第一個數組裡面取到了L1這個位置,L1後面的那個元素假定為R1,那麼根據我們前面所說第二個陣列取到的肯定就是k-L1 -2 。
比如:a1 = [1,5,7,9] a2 = [2,10,18]
,如果想取top4 ,並且假如L1為1這個位置,代表a1中取了2個元素,那麼a2中也只能再取2元素,位置為 = 4 -L1-1-1。
從上面例子我們也看出來了為什麼叫做中心擴散了,如果是求中位數的話,我們一般把L1 = n/2
,然後基於這個點,再做調整。
還拿上面那個例子來說,經過一次取肯定不行,因為既然是取top 4那麼我們要求取到的元素左邊都要小於右邊的,這個對於L1和R1是肯定的,因為a1本身有序,但是L2和R1以及L1和R2就不一定了,比如上面例子a2[L2] >a1[R1],所以我們就需要作出相應調整,我們把L2向左擴散就行了,就是把L2向左移一個位置,因為L2向左移了,所以對應L1應該向右移,這樣幾次操作就會找到topk的位置。
兩個小細節問題:
- 兩個陣列的和是奇數還是偶數,比如
[1,2],[3,4]
,這樣的話最終中位數的值是(2+3/2),如果是[1,2],[3,4,5]
結果就是(3),所以最後要做判斷。左邊的取最大的,右邊用最小的。 - 如果L1或者L2移動到小於0的位置了,那麼就把對應位置的值賦為-inf(負無窮),同樣如果超過了長度就把R對應位置賦為inf。
舉個栗子
初始情況:
a1 = [1,2] a2 = [3,4]
a1長度n = 2,a2長度m = 2
初始化:
L1 = n/2 =1,R1 = L1+1=2 L2 = (m+n)/2 - L1-1-1 = -1,R2 = L2+1 = 0 L1_v = a1[L1] = 2,R1_v = inf L2_v = -inf,R2_v = 3
然後進行比較因為L1_v<R2_v and L2_v<R2_v
所以剛好取到了,又因為是偶數所以最終結果就是(max(L1_v,L2_v)+min(R1_v,R2_v))/2 = 2.5
python程式碼
程式碼裡面有些小細節的處理,比如每次都把第一個陣列置位最短的,因為擴散是從第一個開始的,這樣可以節省時間,還有就是如果有一個為空的怎麼辦。
還有注意python中整除操作。
# 68 ms
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
# 中心擴散法
# > ref https://blog.csdn.net/hk2291976/article/details/51107778
# nums1是最短的那個
n = len(nums1)
m = len(nums2)
if m<n:
return self.findMedianSortedArrays(nums2,nums1)
# 避免有個為空
if n == 0:
#m//2 -(m+1)%2因為是奇數的話(m+1)%2 =0偶數的話等於1,相當於用數學避免一個ifelse
return (nums2[m//2] +nums2[m//2 -(m+1)%2])/2.0
L1 = n//2
R1 = L1+1
L2 = (m+n)//2 - (L1+1)-1
R2 = L2+1
L1_v = nums1[L1] if L1>-1 else float("-inf")
R1_v = nums1[R1] if R1<n else float("inf")
L2_v = nums2[L2] if L2>-1 else float("-inf")
R2_v = nums2[R2] if R2<m else float("inf")
# 兩個都滿足擴散結束
while L1_v >R2_v or L2_v>R1_v:
# L1向左邊擴散並且重新計算其他的值
if L1_v >R2_v:
L1 = L1 -1
R1 = R1-1
R1_v = L1_v
L1_v = nums1[L1] if L1>-1 else float("-inf")
L2 = L2+1
R2 = R2 +1
L2_v = R2_v
R2_v = nums2[R2] if R2<m else float("inf")
# L2向右邊擴散並且重新計算其他的值
else:
L2 = L2 -1
R2 = R2 -1
R2_v = L2_v
L2_v = nums2[L2] if L2>-1 else float("-inf")
L1 = L1+1
R1 = R1+1
L1_v = R1_v
R1_v = nums1[R1] if R1<n else float("inf")
if (m+n) %2 !=0:
return min(R1_v,R2_v)
else:
return (max(L1_v,L2_v)+min(R1_v,R2_v))/2.0
參考:
我是參考這篇部落格寫出來的,並且該部落格給出來了另一種優化的方法,就是可以避免奇偶,但是由於把陣列擴大了,所以耗時較長,測試為112 ms,不過他的部落格寫的比較清楚,所以如果我寫的看不太懂的話,可以參考這個博文,然後回來用我這個程式碼。