[Leetcode 4] 两个排序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个升序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
示例 2:
 

解法一:双指针

用两个指针分别遍历这两个数组,每次选出较小的元素加入新的数组,最终得到的数组也是有序的,时间复杂度是 O(m+n)。但这种做法使用了额外 O(m+n) 的空间。实际上并不用真的把它们合成一个大数组,因为我们需要关心中间的(m + n) / 2 - 1(m + n) / 2个元素,不需要真的保存整个数组
偶数的情况要取两个中间值,所以我们记住上一个值 prev 与当前值 curr
用两个指针遍历时,值小的指针前进;如果一个走完了,另一个接着走。
同时用一个计数器记录我们已经“走过”了多少个元素。一旦到达中间位置,就返回curr或者prevcurr的平均数。
复杂度
  • 时间:O(m+n)
  • 空间:O(1)

解法二:二分查找法(最优解)

中位数的定义是:把“合并后的整体”一分为二,中位数所处的分割位置使得:
  • 左半与右半的大小尽量相等(总长度是奇数时,右半多一个),
  • 左半所有元素 ≤ 右半所有元素
  • m = len(A), n = len(B),
    • total = m + nhalf = total // 2
  • total 为偶数:左半应当正好 half 个元素。
  • total 为奇数:左半half 个、右半 half+1 个元素。
为了高效找到中位数所处的分割位置,我们在较短的数组 A 上做二分查找,取 A 的前 i 个。然后取 Bhalf-i个, 一起组成左半。其余元素组成右半边。
因为要保持左半的大小为half, A上的分割点 i 自然就决定了B上的分割点 j,所以只需要在A 上做二分查找正确的i
然后检查这四个边界元素:maxLeftA = A[i-1], minRightA = A[i], maxLeftB = B[j-1], minRightB = B[j]
notion image
如果同时满足:
  • maxLeftA <= minRightB (已知 maxLeftB <= minRightB
  • maxLeftB <= minRightA (已知 maxLeftA <= minRightA
那么就说明:
  • 左半边的最大值是 max (maxLeftA, maxLeftB)
  • 右半边的最小值是 min (minRightA, minRightB)
也就满足了:
  • 左边最大值 ≤ 右边最小值
那这正是我们想要的条件,因此说明当前分割是正确的,得到正确的等分
然后计算中位数:
  • 若总长为奇数,中位数是 min(minRightA, minRightB)
  • 若总长为偶数,中位数是 max(maxLeftA, maxLeftB)min(minRightA, minRightB) 的平均值。
若条件不满足,移动 i 以找到正确的等分
  • maxLeftA > minRightB :表示A左半的最大值比 B 右半的最小值大了
    • 这违背了“左半 ≤ 右半”的目标,说明 i 取大了(切得太右),i 向左移
  • 反之即maxLeftB > minRightA:表示B左半的最大值 比 A 右半的最小值 大了
    • 这也违背了“左半 ≤ 右半”的目标,说明 i 取小了(切得太左),i 向右移。
复杂度
  • 时间:O(log min(m, n))(只在较短的数组A上二分)。
  • 空间:O(1)
Buy Me a Coffee
上一篇
[Leetcode 3] 无重复字符的最长子串
下一篇
[Leetcode 5] 最长回文子串