1. 程式人生 > >Leetcode 4. Median of Two Sorted Arrays 尋找兩個有序陣列的中位數

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。
請你找出這兩個有序陣列的中位數,並且要求演算法的時間複雜度為 O ( l

o g ( m + n ) ) 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

題目解釋

這個題目有一種變形可以解釋成求Topk問題,比如給兩個有序陣列,給出一種演算法求出第k小元素,對於中位數問題相當於求第(m+n)/2個元素,這道題的難點不在於題目,而在於其複雜度,我想 O (

m + n ) O(m+n) 的演算法還是比較容易想的到的,直接把兩個有序表合併,然後無論是求Top幾我們都能輕鬆的求出來,但是這樣的話和題目要求不符,因為題目要求 O ( l o g ( m + n ) ) O(log(m + n))

這裡給出一種中心擴散法,我沒有計算其複雜度是多少,但是提交上去其耗時為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. 兩個陣列的和是奇數還是偶數,比如[1,2],[3,4] ,這樣的話最終中位數的值是(2+3/2),如果是[1,2],[3,4,5]結果就是(3),所以最後要做判斷。左邊的取最大的,右邊用最小的。
  2. 如果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,不過他的部落格寫的比較清楚,所以如果我寫的看不太懂的話,可以參考這個博文,然後回來用我這個程式碼。

https://blog.csdn.net/hk2291976/article/details/51107778