题目:
题目链接: https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
中位数概念: https://baike.baidu.com/item/%E4%B8%AD%E4%BD%8D%E6%95%B0
解题思路:
方法一: 归并排序
时间复杂度:O(M + N)
利用归并排序的思想,拼接并排序两个数组,并利用合并后的有序数组,获取中位数
方法二:转换为寻找第k小的数字
时间复杂度:O(log(M + N))
假设两个原始数组如下:
nums1(len1 = 5): | 1 | 3 | 5 | 7 | 9 | |
nums2(len2 = 6): | 0 | 2 | 4 | 6 | 8 | 10 |
总长度total_len = len1 + len2 = 11,中位数应该为其中的第index = (11 + 1) / 2 = 6个数字
在对数组进行遍历时,同时获取两个数组的前 half_idx = index / 2 = 3个数组进行比对(如果index为奇数,则向前取整),如下图:
nums1 | 1 | 3 | 5 | 7 | 9 | |
nums2 | 0 | 2 | 4 | 6 | 8 | 10 |
然后比对两个数组中截取序列中最后一个数字的大小,比对时发现4 < 5,则nums2的前3个数字,一定都不是按顺序的第6个数字,此时将nums2的前3个元素删除(不需要真的删除,可以添加一个start_idx记录),更新后的数组如下:
nums1 | 1 | 3 | 5 | 7 | 9 |
nums2 | 6 | 8 | 10 |
更新index = index - 3 = 3,再次进行上述步骤,此时half_idx = index / 2 = 3 / 2 = 1,如下图:
nums1 | 1 | 3 | 5 | 7 | 9 |
nums2 | 6 | 8 | 10 |
此时1 < 6,所以删除nums1的前1个元素,更新index = index - 1 = 3 - 1 = 2,half_idx = index / 2 = 2 / 2 = 1,继续比对:
nums1 | 3 | 5 | 7 | 9 |
nums2 | 6 | 8 | 10 |
3 < 6,继续删除nums1的前1个元素,更新index = index - 1 = 2 - 1 = 1,half_idx = index / 2 = 1 / 2 = 0,停止截取,此时两个数组结果如下:
nums1 | 5 | 7 | 9 |
nums2 | 6 | 8 | 10 |
此时需要分情况考虑:
- 如果原始total_len为奇数,返回min(nums1[0], nums2[0])
- 如果原始total_len为偶数,返回(nums1[0] + nums2[0]) / 2
方法3:变种二分法
时间复杂度:O(log(min(M, N)))
假设两个原始数组如下:
nums1 | 1 | 3 | 5 | 7 | 9 | |
nums2 | 0 | 2 | 4 | 6 | 8 | 10 |
对于每个数组,都可以使用一个分割线,分割为左右两部分,如下图(中间空的列,当做分割线):
nums1 | 1 | 3 | 5 | 7 | 9 | ||
nums2 | 0 | 2 | 4 | 6 | 8 | 10 |
这样子就将两个数组都分割为两部分,我们把两个数组的左半边和右半边分别合并为left_part、right_part
分别设变量含义如下:
i : nums1左半部分的长度,也是右半部分第一个数字的下标,对于上述数组,即数字7在nums1中的下标
j : nums2左半部分的长度,也是右半部分第一个数字的下标,对于上述数组,即数字6在nums2中的下标
m : nums1的长度
n : nums2的长度
m - i : nums1右半部分的长度
n - j : nums2右半部分的长度
left_part :两个数组左半部分的总集
right_part :两个数组右半部分的总集
这样,我们只要满足下面几个条件,就可以直接通过i、j获得中位数:
- left_part中数字总数,与right_part数字总数相等,或者相差1
- left_part中的最大数字,小于right_part中的最小数字
那么如何更新i和j,直到符合条件呢,循环时候的流程如下:
- 初始化i的最小值i_min = 0, 最大值i_max = m,长度一半为half_len = (i_min + i_max + 1) // 2
- 更新当前的i = (i_min + i_max) // 2, j = half_len - i
- 如果i > 0 && j < n && nums1[i - 1] > nums2[j],说明当前nums1左半部分的最大值,比nums2右半部分的最小值要大,不符合要求,需要减小nums1左半部分的最大值,即将i向左移动,同时会导致j在nums2中向右移动,更新i_max = i - 1,并继续循环
- 如果j > 0 && i < m && nums2[j - 1] > nums1[i],说明当前nums2左半部分的最大值,比nums1右半部分的最小值要大,不符合要求,需要增大nums1左半部分的最大值,即将i向右移动,同时会导致j在nums2中向左移动,更新i_min = i + 1,并继续循环
- 其他情况,说明找到了中位数,此时按照下记逻辑进行左半部分最大值left_max和右半部分最小值right_min的计算
- left_max计算逻辑
- 如果i == 0,说明nums1所有数字都属于right_part,所以left_max = nums2[j - 1]
- 如果j == 0,说明nums2所有数字都属于right_part,所以left_max = nums1[i - 1]
- 其余情况,说明nums1和nums2都有部分属于left_part,则left_max = max(nums1[i - 1], nums2[j - 1])
- right_max计算逻辑
- 如果i == m,说明nums1所有数字都属于left_part,所以right_min = nums2[j]
- 如果j == n, 说明nums2所有数字都属于left_part,所以right_min = nums1[i]
- 其他情况,说明nums1和nums2都有部分属于right_part,所以right_min = min(nums1[i], nums2[j])
- 计算完成left_max和right_min之后,如果总数字个数为奇数个,直接返回left_max即可,如果为偶数个,则返回(left_max + right_min) / 2
代码实现(只实现了方法三):
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
len1, len2 = len(nums1), len(nums2)
if len1 > len2:
nums1, nums2 = nums2, nums1
len1, len2 = len2, len1
if len2 == 0:
return 0
imin, imax, half_len = 0, len1, (len1 + len2 + 1) // 2
while imin <= imax:
i = (imin + imax) // 2
j = half_len - i
if i > 0 and j < len2 and nums1[i - 1] > nums2[j]:
imax = i - 1
elif j > 0 and i < len1 and nums2[j - 1] > nums1[i]:
imin = i + 1
else:
left_max, right_min = 0, 0
if i == 0:
left_max = nums2[j - 1]
elif j == 0:
left_max = nums1[i - 1]
else:
left_max = max(nums1[i - 1], nums2[j - 1])
if (len1 + len2) % 2 == 1:
return left_max
if i == len1:
right_min = nums2[j]
elif j == len2:
right_min = nums1[i]
else:
right_min = min(nums1[i], nums2[j])
return (left_max + right_min) / 2