LeetCode | 寻找两个正序数组的中位数

1182 字
6 分钟
LeetCode | 寻找两个正序数组的中位数

分类:二分查找 / 数组

写在前面#

这题应该是 Hot 100 里面最难的一道了。网上有传言戏称:“面试出这题感觉就是不想要你”。

面试官还请手下留情啊 (๑・́ω・̀๑)

题目#

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的中位数。

算法的时间复杂度应该为 O(log (m+n))

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

讲解#

这道题最容易想到的方法是把两个数组合并,再找中位数。这样很好理解,但是需要看完所有元素,时间复杂度是 O(m+n),不满足题目要求。

最优方法是二分查找。不过这里不是二分查找某个具体数字,而是二分查找一个正确的“切分位置”。

先不要看二分,先看中位数本身。

如果合并后的数组是 [1, 2, 3, 4, 5],可以把它分成左边 [1, 2, 3] 和右边 [4, 5]。总长度是奇数,左边比右边多一个数,中位数就是左边最大的数,也就是 3

如果合并后的数组是 [1, 2, 3, 4],可以把它分成左边 [1, 2] 和右边 [3, 4]。总长度是偶数,中位数就是左边最大值和右边最小值的平均数,也就是 (2 + 3) / 2

所以这道题真正要找的不是合并后的完整数组,而是一个切分方式。这个切分方式要满足两件事:左边的数量是对的,并且左边所有数都小于等于右边所有数。

假设在 nums1 里切 i 个数到左边,在 nums2 里切 j 个数到左边。为了保证左边数量正确,需要让 i + j = (m + n + 1) // 2。所以只要确定了 ij 就能跟着确定:j = (m + n + 1) // 2 - i

这就是为什么可以只在一个数组里二分:我们只需要二分 i,然后用总数量关系算出 j

为了让二分更安全,代码先保证 nums1 是较短的数组。这样 i 的范围更小,j 也更不容易越界。

下面这张图展示的是一次合法切分:

两个正序数组的切分示意
两个正序数组的切分示意

每次切分后,只需要看切口附近四个数:nums1_leftnums1_rightnums2_leftnums2_right

因为 nums1nums2 本身都是有序的,所以 nums1 左边最大的数就是 nums1_left,右边最小的数就是 nums1_rightnums2 也是同理。

只要 nums1_left <= nums2_right,就说明 nums1 左边不会比 nums2 右边大。只要 nums2_left <= nums1_right,就说明 nums2 左边不会比 nums1 右边大。两个条件都满足,就代表整个左半边都小于等于整个右半边,这个切分就是正确的。

如果 nums1_left > nums2_right,说明 nums1 左边取多了,i 要往左移。否则说明 nums1 左边取少了,i 要往右移。

找到正确切分以后,中位数就只和切口旁边的数有关。如果总长度是奇数,中位数是 max(nums1_left, nums2_left);如果总长度是偶数,中位数是 max(nums1_left, nums2_left)min(nums1_right, nums2_right) 的平均数。

这里还用了 float("-inf")float("inf") 处理边界。比如 i == 0 时,nums1 左边没有数,就把 nums1_left 看成负无穷;i == m 时,nums1 右边没有数,就把 nums1_right 看成正无穷。这样代码就不用单独为“切在最左边”或“切在最右边”写很多判断。

复杂度#

  • 合并数组:
    • 时间复杂度:O(m+n)
    • 空间复杂度:O(m+n)
  • 二分切分:
    • 时间复杂度:O(log(min(m,n)))
    • 空间复杂度:O(1)

代码#

源码#

from typing import List
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if len(nums1) > len(nums2):
return self.findMedianSortedArrays(nums2, nums1)
m = len(nums1)
n = len(nums2)
total_left = (m + n + 1) // 2
left = 0
right = m
while left <= right:
i = (left + right) // 2
j = total_left - i
nums1_left = float("-inf") if i == 0 else nums1[i - 1]
nums1_right = float("inf") if i == m else nums1[i]
nums2_left = float("-inf") if j == 0 else nums2[j - 1]
nums2_right = float("inf") if j == n else nums2[j]
if nums1_left <= nums2_right and nums2_left <= nums1_right:
if (m + n) % 2 == 1:
return float(max(nums1_left, nums2_left))
return (max(nums1_left, nums2_left) + min(nums1_right, nums2_right)) / 2
if nums1_left > nums2_right:
right = i - 1
else:
left = i + 1
return 0.0

测试#

from src.python._004_Median_of_Two_Sorted_Arrays import Solution
sol = Solution()
assert sol.findMedianSortedArrays([1, 3], [2]) == 2.0
assert sol.findMedianSortedArrays([1, 2], [3, 4]) == 2.5
assert sol.findMedianSortedArrays([], [1]) == 1.0
assert sol.findMedianSortedArrays([2], []) == 2.0
assert sol.findMedianSortedArrays([0, 0], [0, 0]) == 0.0
print("PASS")

参考资料#

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LeetCode | 寻找两个正序数组的中位数
https://leetcode.cn/problems/median-of-two-sorted-arrays/
作者
平昊阳
发布于
2026-08-08
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
66
分类
16
标签
93
总字数
477,284
运行时长
0
最后活动
0 天前

文章目录