来源:力扣
-
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
-
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 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
分析
1、暴力解法:调包加排序
- 合并数组为nums = nums1 + nums2;
- 对其进行排序sorted(nums);
- 直接判断len(nums)的奇偶性,取中间的索引对应的值。
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
nums = nums1 + nums2
# nums.sort()
nums = sorted(nums)
n = len(nums)
if n % 2 == 1:
return nums[n // 2]
else:
return (nums[n//2] + nums[n//2 - 1])/2
2、双指针:先排序合并再取中间值
- 这里首先参考力扣88题
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
# 两种解法:双指针从前都后移动和从后往前移动
#该题要求合并到nums1中,而不是新建一个,所以使用sorted函数时候要注意nums1[:]=bulabula,这样就不会修改nums1
class Solution(object):
def merge(self, nums1, m, nums2, n):
"""
:type nums1: List[int]
:type m: int
:type nums2: List[int]
:type n: int
:rtype: void Do not return anything, modify nums1 in-place instead.
"""
# Make a copy of nums1.
nums1_copy = nums1[:m]
nums1[:] = []
# Two get pointers for nums1_copy and nums2.
p1 = 0
p2 = 0
# Compare elements from nums1_copy and nums2
# and add the smallest one into nums1.
while p1 < m and p2 < n:
if nums1_copy[p1] < nums2[p2]:
nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
# if there are still elements to add
if p1 < m:
nums1[p1 + p2:] = nums1_copy[p1:]
if p2 < n:
nums1[p1 + p2:] = nums2[p2:]
#时间复杂度O(n+m),空间复杂度O(m)
解法2:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
# nums1[:] = sorted(nums1[:m] + nums2)
p1 = m - 1
p2 = n - 1
p = m+n-1
while p1>=0 and p2>=0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p-=1
# 如果nums1为空,则单独处理
nums1[:p2+1] = nums2[:p2+1]
针对这个题,我们利用倒序双指针
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m, n = len(nums1), len(nums2)
p1 = m-1
p2 = n-1
p = m + n - 1
# 单独定义一个列表空间,用来存放新的有序数组
nums = [0]*(m+n)
# 终止条件就是有一个数组遍历完
while p1>=0 and p2>=0:
if nums1[p1] >= nums2[p2]:
nums[p] = nums1[p1]
p1 -= 1
else:
nums[p] = nums2[p2]
p2 -= 1
p -= 1
# 额外判断某一个数组遍历完,需要把另外一个数组直接拼接进去
if p1<0:
nums[:p2+1] = nums2[:p2+1]
if p2<0:
nums[:p1+1] = nums1[:p1+1]
# 判断数组长度的奇偶性
if len(nums)%2 == 1:
return nums[(m+n)//2]
else:
return (nums[(m+n)//2]+nums[(m+n)//2-1])/2
3、二分查找(第K小数)
根据中位数的定义,当 m+nm+nm+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2(m+n)/2(m+n)/2 个元素,当 m+nm+nm+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2(m+n)/2(m+n)/2 个元素和第 (m+n)/2+1(m+n)/2+1(m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 kkk 小的数,其中 kkk 为 (m+n)/2(m+n)/2(m+n)/2 或 (m+n)/2+1(m+n)/2+1(m+n)/2+1。
假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],其中 / 表示整数除法。由于 A[k/2−1] 和 B[k/2−1]的前面分别有 A[0 : k/2−2] 和 B[0 :k/2−2],即 k/2−1个元素,对于 A[k/2−1]和 B[k/2−1]中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k−2 个元素比它小,那么它就不能是第 k 小的数了。
因此我们可以归纳出三种情况:
-
如果 A[k/2−1]<B[k/2−1],则比 A[k/2−1] 小的数最多只有 A的前 k/2−1个数和 B的前 k/2−1个数,即比 A[k/2−1] 小的数最多只有 k−2个,因此 A[k/2−1]不可能是第 k 个数,A[0]到 A[k/2−1]也都不可能是第 k 个数,可以全部排除。
-
如果 A[k/2−1]>B[k/2−1],则可以排除 B[0] 到 B[k/2−1]。
-
如果 A[k/2−1]=B[k/2−1],则可以归入第一种情况处理。
可以看到,比较 A[k/2−1]和 B[k/2−1]之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。
有以下三种情况需要特殊处理:
-
如果 A[k/2−1] 或者 B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 k 的值,而不能直接将 k 减去 k/2。
-
如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。
-
如果 k=1,我们只要返回两个数组首元素的最小值即可。
用一个例子说明上述算法。假设两个有序数组如下:
A: 1 3 4 9
B: 1 2 3 4 5 6 7 8 9
两个有序数组的长度分别是 4 和 9,长度之和是 13,中位数是两个有序数组中的第 7个元素,因此需要找到第 k=7 个元素。
比较两个有序数组中下标为 k/2−1=2 的数,即 A[2]和 B[2],如下面所示:
A: 1 3 4 9
-------- ↑
B: 1 2 3 4 5 6 7 8 9
-------- ↑
由于 A[2]>B[2],因此排除 B[0]到 B[2],即数组 B 的下标偏移(offset)变为 3,同时更新 k的值:k=k−k/2=4。(其实为K减去偏移量来更新K,K -= offset)
下一步寻找,比较两个有序数组中下标为 k/2−1=1 的数,即 A[1]和 B[4],如下面所示,其中方括号部分表示已经被排除的数。
A: 1 3 4 9
------ ↑
B: [1 2 3] 4 5 6 7 8 9
--------------- ↑
由于 A[1]<B[4],因此排除 A[0]到 A[1],即数组 A的下标偏移变为 2,同时更新 k 的值:k=k−k/2=2。
下一步寻找,比较两个有序数组中下标为 k/2−1=0的数,即比较 A[2]和 B[3],如下面所示,其中方括号部分表示已经被排除的数。
A: [1 3] 4 9
---------- ↑
B: [1 2 3] 4 5 6 7 8 9
------------ ↑
由于 A[2]=B[3],根据之前的规则,排除 A中的元素,因此排除 A[2],即数组 A的下标偏移变为 3,同时更新 k 的值: k=k−k/2=1。
由于 k 的值变成 1,因此比较两个有序数组中的未排除下标范围内的第一个数,其中较小的数即为第 k 个数,由于 A[3]>B[3],因此第 k 个数是 B[3]=4。
A: [1 3 4] 9
↑
B: [1 2 3] 4 5 6 7 8 9
↑
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 第K小数
# def getK(k):
# index1, index2 = 0, 0
# while True:
#终止条件
# 一个为空,直接返回另一个第K个元素,这时的K已经更新过了
# if index1 == m:
# return nums2[index2 + k - 1]
# if index2 == n:
# return nums1[index1 + k - 1]
#K为1说明已经查找到K,只需要比较这时小的元素即可
# if k == 1:
# return min(nums1[index1], nums2[index2])
# new1 = min(index1 + k//2 -1, m-1)
# new2 = min(index2 + k//2 -1, n-1)
# p1, p2 = nums1[new1], nums2[new2]
# if p1 <= p2:
# k -= new1 - index1 + 1
# index1 = new1 + 1
# else:
# k -= new2 -index2 + 1
# index2 = new2 + 1
# m, n = len(nums1), len(nums2)
# num = m + n
# if num % 2 == 1:
# return getK((num + 1) // 2)
# else:
# return (getK(num//2)+getK((num//2 + 1)))/2
# 暴力解法,直接合并
# nums = nums1 + nums2
# # nums.sort()
# nums = sorted(nums)
# n = len(nums)
# if n % 2 == 1:
# return nums[n // 2]
# else:
# return (nums[n//2] + nums[n//2 - 1])/2
# 双指针
# 左右指针初始值,中位数位置
m, n = len(nums1), len(nums2)
p1 = m-1
p2 = n-1
p = m + n - 1
nums = [0]*(m+n)
while p1>=0 and p2>=0:
if nums1[p1] >= nums2[p2]:
nums[p] = nums1[p1]
p1 -= 1
else:
nums[p] = nums2[p2]
p2 -= 1
p -= 1
if p1<0:
nums[:p2+1] = nums2[:p2+1]
if p2<0:
nums[:p1+1] = nums1[:p1+1]
if len(nums)%2 == 1:
return nums[(m+n)//2]
else:
return (nums[(m+n)//2]+nums[(m+n)//2-1])/2