1. 程式人生 > >Leetcode演算法——4、兩個有序陣列的中位數

Leetcode演算法——4、兩個有序陣列的中位數

題目

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)).
You may assume nums1 and nums2 cannot be both empty.

有兩個已經排好序(升序)的陣列 nums1 和 nums2,大小分別為 m 和 n。
要求找到兩個陣列的中位數。時間複雜度需要為 O(log(m+n))。
兩個陣列不會同時為空。

示例:
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0

Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

思路

兩個陣列都是有序的,讓我想起了歸併排序:將兩個有序數組合在一起並排序,只需要用兩個指標分別掃描兩個陣列,每次指標+1並比較大小。
這道題可以直接使用歸併排序,然後找到中位數,但是這樣複雜度為O(m+n),不滿足條件。
由於這道題只需要找到中位數,其他元素是否排序無關緊要,因此,兩個指標不必每次只+1,而是每次加一個最合適的值,使得整體複雜度最小。這個合適的值,一般等於還差多少位到終點的一半,這樣,複雜度便為 O(log(m+n))。

python實現

# -*- coding: utf-8 -*-

def findMedianSortedArrays(nums1, nums2):
    """
    :type nums1: List[int]
    :type nums2: List[int]
    :rtype: float

    思路:
    兩個陣列都是有序的,讓我想起了歸併排序:將兩個有序數組合在一起並排序,只需要用兩個指標分別掃描兩個陣列,每次指標+1並比較大小。
    這道題可以直接使用歸併排序,然後找到中位數,但是這樣複雜度為O(m+n),不滿足條件。
    由於這道題只需要找到中位數,其他元素是否排序無關緊要,因此,兩個指標不必每次只+1,而是每次加一個最合適的值,使得整體複雜度最小。
    """
def fun_rec(nums1, i, nums2, j, k): ''' 找到兩個陣列的整體排序後的第 i+j+k 個數。分別已經掃描過了前i個和前j個,需要在此基礎上找到第 k 個數。 ''' # 遞迴結束條件 if i == len(nums1): return nums2[j+k-1] if j == len(nums2): return nums1[i+k-1] if k == 1: return min(nums1[i], nums2[j]) # 保證前一個數組的剩餘長度比後一個數組要短,這樣只要控制好前一個數組的下標,就不用判斷後一個數組是否會溢位 if len(nums1) - i > len(nums2) - j: return fun_rec(nums2, j, nums1, i, k) # 兩個指標分別從i和j開始,都直接加上 k/2,然後比較大小,選出較小的那個陣列,繼續和另一個數組進行遞迴。 ii = i + int(k/2) ii = min(ii, len(nums1)) # 不能溢位 jj = i+j+k-ii # 保證 (ii-i)+(jj-j) == k,即 ii和jj的增量的和正好是k,這樣可以達到每次儘可能增加最大的下標增量,而又不會越過第k個數 if nums1[ii-1] == nums2[jj-1]: # 相等,說明正好是第k個數 return nums1[ii-1] if nums1[ii-1] < nums2[jj-1]: # 前一個值小,說明在最終第k個數,肯定在ii之後 return fun_rec(nums1, ii, nums2, j, k+i-ii) # 始終保持 fun_rec 函式的第2、4、5的引數的和等於 i+j+k return fun_rec(nums1, i, nums2, jj, k+j-jj) # 判斷應該尋找第幾個數 total = len(nums1) + len(nums2) if total % 2 == 1: # 奇數,需要尋找最中間的那個數 return fun_rec(nums1, 0, nums2, 0, int((total+1)/2)) else: # 偶數,需要求中間兩個數的均值 return (fun_rec(nums1, 0, nums2, 0, int(total/2)) + fun_rec(nums1, 0, nums2, 0, int(total/2+1))) / 2 def findMedianSortedArrays2(nums1, nums2): """ :type nums1: List[int] :type nums2: List[int] :rtype: float 上一個方法有個缺點,就是如果是偶數,那麼在求中間兩個數的時候,大部分過程是重複的。 改進:將fun_rec輸出第k個數改為輸出第k個數和第k+1個數。 """ def fun_rec(nums1, i, nums2, j, k): ''' 找到兩個陣列的整體排序後的第 i+j+k 個數和右鄰的數。分別已經掃描過了前i個和前j個,需要在此基礎上找到第 k 個數。 ''' # 遞迴結束條件 if i == len(nums1): return (nums2[j+k-1], nums2[j+k]) if j == len(nums2): return (nums1[i+k-1], nums1[i+k]) if k == 1: if nums1[i] < nums2[j]: return (nums1[i], find_next(nums1, i, nums2, j-1)) else: return (nums2[j], find_next(nums2, j, nums1, i-1)) # 保證前一個數組的剩餘長度比後一個數組要短,這樣只要控制好前一個數組的下標,就不用判斷後一個數組是否會溢位 if len(nums1) - i > len(nums2) - j: return fun_rec(nums2, j, nums1, i, k) # 兩個指標分別從i和j開始,都直接加上 k/2,然後比較大小,選出較小的那個陣列,繼續和另一個數組進行遞迴。 ii = i + int(k/2) ii = min(ii, len(nums1)) # 不能溢位 jj = i+j+k-ii # 保證 (ii-i)+(jj-j) == k,即 ii和jj的增量的和正好是k,這樣可以達到每次儘可能增加最大的下標增量,而又不會越過第k個數 if nums1[ii-1] == nums2[jj-1]: # 相等,說明正好是第k個數 return (nums1[ii-1], find_next(nums1, ii-1, nums2, jj-1)) if nums1[ii-1] < nums2[jj-1]: # 前一個值小,說明在最終第k個數,肯定在ii之後 return fun_rec(nums1, ii, nums2, j, k+i-ii) # 始終保持 fun_rec 函式的第2、4、5的引數的和等於 i+j+k return fun_rec(nums1, i, nums2, jj, k+j-jj) def find_next(nums1, i, nums2, j): ''' nums1已經掃描到了i,nums2已經掃描到了j。 返回 nums1[i] 的下一個大的元素。 ''' if i == len(nums1) - 1: return nums2[j+1] return min(nums1[i+1], nums2[j+1]) # 判斷應該尋找第幾個數 if len(nums1)== 0 and len(nums2) == 1: return nums2[0] if len(nums2)== 0 and len(nums1) == 1: return nums1[0] total = len(nums1) + len(nums2) result = fun_rec(nums1, 0, nums2, 0, int((total+1)/2)) if total % 2 == 1: # 奇數,需要尋找最中間的那個數 return result[0] else: # 偶數,需要求中間兩個數的均值 return (result[0]+result[1])/2 if '__main__' == __name__: nums1 = [1, 3] nums2 = [2] print(findMedianSortedArrays2(nums1, nums2)) nums1 = [1, 2] nums2 = [3, 4] print(findMedianSortedArrays2(nums1, nums2))