1. 程式人生 > 其它 >【leetcode第4題】尋找兩個正序陣列的中位數

【leetcode第4題】尋找兩個正序陣列的中位數

技術標籤:演算法方向演算法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題官方題解