4.寻找两个正序数组的中位数
4.1题目
1.2解决方法
方法一
直接将两个数组合并,但不符合时间复杂度=O(log (m+n)) ,速度:17ms
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
m = len(nums1)
n = len(nums2)
merge = nums1+nums2
merge.sort()
x = (m+n)//2
y = (m+n)%2
if y==1:
median = merge[x]
else:
median = (merge[x-1]+merge[x])/2.0
return median
方法二
二分法,符合时间复杂度=O(log (m+n)),速度:21ms
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
if len(nums1)>len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
imin, imax, half_len = 0, m, (m+n+1)//2
while imin <=imax:
i = (imin + imax) //2
j = half_len - i
if i < m and nums1[i] < nums2[j-1]:
imin = i + 1
elif i > 0 and nums1[i-1] > nums2[j]:
imax = i - 1
else:
if i == 0: max_of_left = nums2[j-1]
elif j == 0: max_of_left = nums1[i-1]
else: max_of_left = max(nums1[i-1], nums2[j-1])
if (m + n) % 2 == 1:
return max_of_left
if i == m: min_of_right = nums2[j]
elif j == n: min_of_right = nums1[i]
else: min_of_right = min(nums1[i], nums2[j])
return (max_of_left + min_of_right) / 2.0
1.3方法二(二分法)解释
当我们需要在两个已排序数组 nums1
和 nums2
中找到它们的中位数时,可以使用二分查找的方法来解决。这里给出的代码使用了这种方法来有效地找到中位数。
1.3.1代码解释及原理
1)算法原理
- 给定两个已排序的数组
nums1
和nums2
,长度分别为m
和n
。 - 中位数定义:如果数组长度之和
(m + n)
是奇数,则中位数是第(m + n) // 2 + 1
个元素;如果是偶数,则中位数是第(m + n) // 2
和第(m + n) // 2 + 1
个元素的平均值。
2) 二分查找策略
- 为了优化查找速度,选择在长度较短的数组
nums1
上进行二分查找。 - 设定
imin
和imax
分别为nums1
中元素的范围,初始时imin = 0
,imax = m
,其中m = len(nums1)
。
3) 二分查找过程
- 计算
i
作为nums1
的分割点:i = (imin + imax) // 2
。 - 计算
j
作为nums2
的分割点:j = half_len - i
,其中half_len = (m + n + 1) // 2
。half_len = (m + n + 1) // 2的原因:
(m + n + 1) // 2
的计算方法确保了在奇数和偶数情况下都能正确地找到中位数的位置,是处理这类问题的常见做法。
i
是nums1
中的分割点索引,它将nums1
分成左右两部分:nums1[0:i-1]
和nums1[i:m-1]
。j
是nums2
中的分割点索引,它将nums2
分成左右两部分:nums2[0:j-1]
和nums2[j:n-1]。
- 根据二分查找的思路,我们要确保左侧部分的元素个数等于右侧部分的元素个数,即
len(nums1[0:i-1]) + len(nums2[0:j-1]) = len(nums1[i:m-1]) + len(nums2[j:n-1])
。为了保证这个条件,我们需要调整i
和j
的值,直到找到满足条件的分割点。
- 根据二分查找的思路,我们要确保左侧部分的元素个数等于右侧部分的元素个数,即
4) 满足条件的分割点
- 如果
i < m
且nums1[i] < nums2[j-1]
,说明i
太小,需要增加i
以增大nums1[i]
,使得左侧部分的元素全部小于右侧部分的元素。 - 如果
i > 0
且nums1[i-1] > nums2[j]
,说明i
太大,需要减小i
以减小nums1[i-1]
,同样保证左侧部分的元素全部小于右侧部分的元素。
5) 边界情况处理
- 对于
i == 0
的情况,意味着nums1
中的所有元素都在右侧,因此左侧部分没有任何元素。这时,我们需要考虑左侧最大值。由于nums2
在右侧的所有元素是nums2[j:n-1]
,那么左侧的最大值应该是nums2[j-1]
,而不是nums2[j]
。这是因为j
是分割点,nums2[j-1]
是在nums2[0:j-1]
中的最后一个元素。 - 因此,在这种情况下,我们取
nums2[j-1]
作为左侧的最大值,因为它是nums2
中左侧部分的最后一个元素。 - 这种设计确保了在每一步迭代中,根据当前的分割点
i
和j
,能够正确地确定左侧和右侧的边界值,从而实现有效的二分查找和中位数计算。 -
确定左侧最大值 (
max_of_left
):- 如果
i
等于 0,说明nums1
中的所有元素都在右侧,因此左侧最大值取nums2[j - 1]
。 - 如果
j
等于 0,说明nums2
中的所有元素都在右侧,因此左侧最大值取nums1[i - 1]
。 - 否则,取
nums1[i - 1]
和nums2[j - 1]
中较大的值作为左侧最大值。
- 如果
-
判断数组长度之和的奇偶性:
- 如果
(m + n) % 2 == 1
,即总元素个数为奇数,则直接返回max_of_left
作为中位数。
- 如果
-
确定右侧最小值 (
min_of_right
):- 如果
i
等于m
,说明nums1
中的所有元素都在左侧,右侧最小值取nums2[j]
。 - 如果
j
等于n
,说明nums2
中的所有元素都在左侧,右侧最小值取nums1[i]
。 - 否则,取
nums1[i]
和nums2[j]
中较小的值作为右侧最小值。
- 如果
-
计算中位数:
- 如果
(m + n) % 2 == 1
,直接返回max_of_left
。 - 否则,返回
(max_of_left + min_of_right) / 2.0
,即左侧最大值和右侧最小值的平均值作为中位数。
- 如果