Leetcode演算法——4、兩個有序陣列的中位數
阿新 • • 發佈:2019-01-26
題目
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))