【leetcode第4題】尋找兩個正序陣列的中位數
阿新 • • 發佈:2021-01-15
技術標籤:演算法方向演算法leetcode二分查詢分治演算法陣列
尋找兩個正序陣列的中位數題解——劃分陣列法
題目內容
給定兩個大小為 m 和 n 的正序(從小到大)陣列 nums1 和 nums2。請你找出並返回這兩個正序陣列的中位數。
# 示例 1:
# 輸入
>>>nums1 = [1,3], nums2 = [2]
# 輸出
>>>2.00000
# 合併陣列 = [1,2,3] ,中位數 2
# 示例 2:
# 輸入
>>>nums1 = [1,2], nums2 = [3,4]
# 輸出
>>> 2.50000
# 合併陣列 = [1,2,3,4] ,中位數 (2 + 3) / 2 = 2.5
# 示例 3:
# 輸入
>>>nums1 = [], nums2 = [1]
# 輸出
>>>1.00000
# 輸入
>>>nums1 = [2], nums2 = []
# 輸出
>>>2.00000
提示:
0 <= m <= 1000 , 0 <= n <= 1000
1 <= m + n <= 2000
-1000000 <= nums1[i], nums2[i] <= 1000000
解題方法
看到這個題我們首先要思考,中位數的作用是什麼。
將一個集合劃分為兩個長度相等的子集,其中一個子集中的元素總是大於另一個子集中的元素。
我們將nums1與nums2分別在 i,j 的位置劃分為左右兩部分試試看:
# 假設: m, n = len(nums1), len(nums2)
left | right
nums1[0, ... , i-1] | nums1[i, ... , m-1]
nums2[0, ... , j-1] | nums2[j, ... , n-1]
若把兩個陣列的左半邊合併成一個新的集合:left,右半邊合併成另一個新的集合:right
我們希望能找到一種劃分方式,使left與right集合恰好是中位數劃分出的左右兩子集
為此我們需要滿足兩個條件:
1.left集合的最大元素小於right集合的最小元素
2.left集合的長度與right集合的長度相同
# 條件1:
# left集合最大元素為 max(nums1[i-1], nums2[j-1])
# right集合最小元素為 min(nums1[i], nums2[j])
# 由於在劃分階段,我們需要遍歷較小長度的陣列以尋找最優方案
# 所以僅控制nums1[i-1]即可
# nums1[i-1] <= nums1[i] # 由於是正序陣列,該條件一定滿足
nums1[i-1] <= nums2[j]
考慮到總長度的奇偶性,我們要考慮更詳細完善的約束條件:
1.若總長度為偶數,中位數實際上由中間的兩個數共同組成,上述條件不用更改
2.若總長度為奇數,我們將中位數放入left集合,使left集合比right集合多一個元素,上述條件需要修正
# 條件2:
# left集合長度為: i + j
# 兩正序陣列總長度為: m + n
# 當總長度為偶數時:
if (m + n) % 2 == 0:
j = (m + n) / 2 - i
# 當總長度為奇數時:
if (m + n) % 2 == 1
j = (m + n + 1) / 2 - i
# 我們發現可以將奇偶情況下的約束合併為一條語句,省去判奇偶的時間:
j = (m + n + 1) // 2 - i
為了更快的找到劃分的位置,我們可以採用二分查詢的方法,快速找到 i 的位置:
# 藉助雙指標left與right來計算 i 的位置
i = (left + right) // 2
# 在最初狀態,i 應該在nums1中間
left, right = 0, m
# 若 i 取大了, 將right指標移至 i 的左邊:
若i太大:
right = i - 1
# 若 i 取小了, 將left指標移至 i 的右邊:
若i太小:
left = i + 1
找到合適的劃分方式後,根據總長度的奇偶情況返回合適的數即可
程式碼實現
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if len(nums1) > len(nums2):
return self.findMedianSortedArrays(nums2, nums1)
MAXNUM = 10**6
m = len(nums1)
n = len(nums2)
# 左右指標,尋找nums1劃分點
left, right = 0, m
# 前半部分中位數, 後半部分中位數
median1, median2 = 0, 0
# nums1劃分點 + nums2劃分點
i, j = 0, 0
numsim1, numsi, numsjm1, numsj = 0, 0, 0, 0
while left <= right:
i = (left + right) // 2
j = (m + n + 1) // 2 - i
# nums1[0...i-1], nums1[i...m-1]
numsim1 = ( nums1[i-1] if i != 0 else -MAXNUM )
numsi = ( nums1[i] if i != m else MAXNUM )
# nums2[0...j-1], nums2[j...n-1]
numsjm1 = ( nums2[j-1] if j != 0 else -MAXNUM )
numsj = ( nums2[j] if j != n else MAXNUM )
if numsim1 <= numsj:
median1, median2 = max( numsim1, numsjm1), min(numsi, numsj)
left = i + 1
else:
right = i - 1
return (median1 + median2) / 2 if (m + n) % 2 == 0 else median1
複雜度分析
時間複雜度:O(logmin(m,n)))。
其中 m 和 n 分別是陣列nums1 和 nums2 的長度。查詢的區間是[0,m],而該區間的長度在每次迴圈之後都會減少為原來的一半。所以,只需要執行 logm 次迴圈。由於每次迴圈中的操作次數是常數,所以時間複雜度為 O(logm)。由於我們可能需要交換 nums1 和 nums2 使得m≤n,因此時間複雜度是 O(logmin(m,n)))。
空間複雜度:O(1)。
執行結果
歡迎各位大佬交流討論
本文參考leecode題庫第4題官方題解