ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LeetCode 0004《寻找两个正序数组的中位数》:基于二分查找的 O(log(m+n)) 解法详解(AlgoNote 算法通关手册)

LeetCode 0004《寻找两个正序数组的中位数》:基于二分查找的 O(log(m+n)) 解法详解(AlgoNote 算法通关手册) 教程文档知识库【免费下载链接】AlgoNote⛽️「算法通关手册」从零开始的「算法与数据结构」学习教程200 道「算法面试热门题目」1000 道「LeetCode 题目解析」持续更新中项目地址https://gitcode.com/gh_mirrors/le/AlgoNote点击查看免费下载导读本篇技术指南围绕「算法通关手册AlgoNote」中 LeetCode 0004《寻找两个正序数组的中位数》题解 展开深入讲解如何在两个有序数组中利用**二分查找Binary Search与分治Divide and Conquer**思想以 $O(\log(mn))$ 的时间复杂度定位中位数。读完本文你将掌握「第 k 小元素」二分查找法的推导逻辑、边界条件处理技巧以及仓库源码中归并排序、二分查找基础算法与本解法之间的关联可直接复刻该解法应对同类面试题。一、题目概述与约束题目给定两个正序从小到大排序数组nums1、nums2找出并返回这两个正序数组的中位数。标签数组、二分查找、分治难度困难约束条件来自 原题解文档算法的时间复杂度应该为 $O(\log(m n))$。nums1.length m。nums2.length n。$0 \le m \le 1000$。$0 \le n \le 1000$。$1 \le m n \le 2000$。$-10^6 \le nums1[i], nums2[i] \le 10^6$。示例 1输入nums1 [1,2], nums2 [3,4] 输出2.50000 解释合并数组 [1,2,3,4] 中位数 (2 3) / 2 2.5示例 2输入nums1 [1,2], nums2 [3,4] 输出2.50000 解释合并数组 [1,2,3,4] 中位数 (2 3) / 2 2.5值得注意由于题目要求 $O(\log(mn))$而 $m, n \le 1000$、$m n \le 2000$直接归并两个数组后取中位数的做法虽然正确但时间复杂度为 $O(mn)$无法满足题目对时间复杂度的硬性要求。这正是本题被标记为「困难」的核心原因——必须利用有序数组的二分性质将查找规模压缩到对数级别。二、解题思路从归并到二分查找2.1 朴素思路归并拼接单个有序数组的中位数就是中间位置元素若中间位置对应两个元素则取二者平均数。如果是两个有序数组一个最直观的办法是用归并的方式拼接成一个大数组归并排序中的merge过程仓库源码 codes/python/01_array/array_sort_merge_sort.py正是「两个有序子数组合并」的典型实现双指针依次取出较小元素直到一方耗尽再追加剩余元素合并后的大数组长度为 $(n1 n2)$其中间位置的元素即为中位数。但正如上文所述归并需要线性时间并不满足 $O(\log(mn))$ 的要求。因此我们需要跳脱「真正合并出完整数组」的思路——只需找到中位数的位置即可。2.2 关键观察中位数把数组切成左右两半设 $n1$、$n2$ 分别为nums1、nums2的长度合并后的总长度为 $(n1 n2)$。观察中位数的定义可以发现一个核心性质中位数把合并后的数组分割成了左右两部分并且左右两部分元素个数相等。由此可以分奇偶两种情况讨论若 $(n1 n2)$ 是奇数中位数是合并后数组中第 $\lfloor \frac{(n1 n2)}{2} \rfloor 1$ 个元素包含中位数在内的单侧元素个数为 $\lfloor \frac{(n1 n2)}{2} \rfloor 1$若 $(n1 n2)$ 是偶数中位数是第 $\lfloor \frac{(n1 n2)}{2} \rfloor$ 与第 $\lfloor \frac{(n1 n2)}{2} \rfloor 1$ 两个元素的平均值单侧元素个数为 $\lfloor \frac{(n1 n2)}{2} \rfloor$。由于是向下取整两种情况下单侧元素个数可以统一写成$$ k \left\lfloor \frac{n1 n2 1}{2} \right\rfloor $$于是原问题被等价转化为一个经典子问题如何在两个有序数组中找出前 k 小的元素位置2.3 问题转化枚举 m1 确定 m2如果从nums1中取出前 $m1 \ (m1 \le k)$ 个元素那么从nums2中就需要取出前 $m2 k - m1$ 个元素。一旦在nums1中确定了合适的 $m1$$m2$ 也随之唯一确定。于是问题进一步收敛为如何从nums1中选取前 $m1$ 个元素使得分割线左半边恰好包含 k 个元素即nums1的第 $m1$ 个元素或nums2的第 $m2 k - m1$ 个元素位于中位线位置。这个「在有序区间中寻找满足条件的分割点」的过程正是二分查找的用武之地。仓库中的二分查找基础文档 docs/01_array/01_13_array_binary_search_01.md 指出二分查找的核心是「每次将查找区间缩小一半」而 docs/01_array/01_14_array_binary_search_02.md 中的**「排除法」思想**——每轮循环优先排除掉一定不包含目标元素的区间仅在可能存在目标的区间内继续查找——正是本题二分查找循环体的设计依据。三、二分查找的实现细节3.1 算法步骤初始化令left指向nums1的头部位置0right指向nums1的尾部位置n1每轮取中间位置作为 $m1$则 $m2 k - m1$。然后比较nums1[m1]与nums2[m2 - 1]若nums1[m1] nums2[m2 - 1]说明nums1中取的元素不够多nums1[m1]不可能是第 k 个元素应右移 $m1$即left m1 1若nums1[m1] nums2[m2 - 1]说明 $m1$ 取值可能偏大按排除法思路收缩右边界即right m1循环结束后$m1$ 即为最终分割位置$m2 k - m1$根据 $(n1 n2)$ 的奇偶性与边界条件计算中位数。3.2 为什么判断nums1[m1]与nums2[m2 - 1]的关系这是整个解法的核心推理点推导如下若nums1[m1] nums2[m2 - 1]则nums1[m1]左侧比它小的元素共有 $m1$ 个即nums1[0] ... nums1[m1 - 1]nums2数组中最多有 $m2 - 1$ 个元素比nums1[m1]小即便nums2[m2 - 1]左侧所有元素都比nums1[m1]小也只有 $m2 - 1$ 个综合来看nums1、nums2中最多有 $m1 m2 - 1 k - 1$ 个元素比nums1[m1]小因此nums1[m1]左侧的 $m1$ 个元素nums1[0] ... nums1[m1 - 1]都不可能是第 k 个元素可以全部排除然后将 $m1$ 右移。这个「排除不可能区间」的过程与 docs/01_array/01_14_array_binary_search_02.md 中排除法的写法完全一致当nums[mid] target时排除[left, mid]区间、继续在[mid 1, right]查找反之收缩右边界为right mid。本题将「目标值」替换成了「满足分割条件的 m1」本质是同一套减治逻辑。3.3 为何选择较短的数组进行二分在代码实现中有一行关键预处理if n1 n2: return self.findMedianSortedArrays(nums2, nums1)交换两个数组保证始终在较短的nums1上做二分。这样做有两个好处二分区间[0, n1]更短迭代次数更少更关键的是保证 $m2 k - m1$ 恒为非负且不超过 $n2$。由于 $m1 \le n1$、$k \lfloor (n1n21)/2 \rfloor \ge n1$可得 $m2 k - m1 \ge k - n1 \ge 0$且 $m2 \le k \le n2$从而nums2[m2 - 1]、nums2[m2]的访问始终安全避免越界。3.4 参考代码以下完整实现继承自 原题解文档 的「思路 1代码」class Solution: def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) - float: n1 len(nums1) n2 len(nums2) if n1 n2: return self.findMedianSortedArrays(nums2, nums1) k (n1 n2 1) // 2 left 0 right n1 while left right: m1 left (right - left) // 2 # 在 nums1 中取前 m1 个元素 m2 k - m1 # 在 nums2 中取前 m2 个元素 if nums1[m1] nums2[m2 - 1]: # 说明 nums1 中所取元素不够多 left m1 1 # 应右移 m1排除左侧不可能区间 else: right m1 # m1 可能偏大收缩右边界 m1 left m2 k - m1 c1 max(float(-inf) if m1 0 else nums1[m1 - 1], float(-inf) if m2 0 else nums2[m2 - 1]) if (n1 n2) % 2 1: return c1 c2 min(float(inf) if m1 n1 else nums1[m1], float(inf) if m2 n2 else nums2[m2]) return (c1 c2) / 2代码要点逐行拆解k (n1 n2 1) // 2统一奇偶两种情况的单侧元素个数m1 left (right - left) // 2用「left (right - left) // 2」而非(left right) // 2计算中间位置避免加法溢出这也是 docs/01_array/01_13_array_binary_search_01.md 中推荐的防溢出写法二分结束后m1 left此时left rightm1即为nums1中应取出的元素个数c1左半区间的最大值即中位线左侧最后一个元素取nums1[m1-1]与nums2[m2-1]的较大者当m1 0或m2 0时用float(-inf)兜底若总长度为奇数左半区间的最大值c1就是中位数直接返回若总长度为偶数还需右半区间的最小值c2取nums1[m1]与nums2[m2]的较小者越界时用float(inf)兜底中位数即为(c1 c2) / 2。边界条件的必要性float(-inf)与float(inf)的引入正是为了覆盖m1 0、m2 0、m1 n1、m2 n2这四类极端情形例如某个数组为空、或某个数组的元素全部被取走保证nums1[m1 - 1]、nums2[m2]等下标不会越界。四、复杂度分析与分治思想印证4.1 复杂度分析时间复杂度$O(\log(m n))$。每轮循环将nums1上的查找区间缩小一半而nums1是两数组中较短的一个$n1 \le (mn)/2$二分迭代次数为 $O(\log n1) O(\log(mn))$每次循环仅做常数次比较与赋值因此总复杂度为 $O(\log(mn))$严格满足题目要求。空间复杂度$O(1)$。全程仅使用left、right、m1、m2、c1、c2等常数个变量未申请与输入规模相关的额外空间递归交换调用仅在第一次发生不随规模增长。4.2 与仓库「分治算法」主题的呼应本题在题目分类中被标记为「数组、二分查找、分治」见 docs/00_preface/00_06_categories_list.md 的二分查找题目列表。对照仓库 docs/07_algorithm/07_03_divide_and_conquer_algorithm.md 中的分治定义分解把「找中位数」分解为「在两个有序数组中找前 k 小元素」再进一步分解为「确定 m1、m2 两个分割点」求解利用二分查找的排除法在nums1上对数级地逼近正确的 m1合并根据奇偶性与边界条件将左右两个分割边界元素c1、c2合并为中位数答案。同时本题朴素做法中「归并两个有序数组」的过程在仓库源码 codes/python/01_array/array_sort_merge_sort.py 中有可直接对照的merge双指针实现。理解这两个维度——「线性归并」与「对数级二分」——的差异正是吃透本题的关键。五、总结与同类题目延伸5.1 核心要点回顾中位数的分割性质中位数把合并数组切分成元素个数相等的左右两部分据此得到统一公式 $k \lfloor (n1n21)/2 \rfloor$枚举 m1 确定 m2在较短数组nums1上二分搜索 m1m2 由 $m2 k - m1$ 唯一确定排除法二分通过比较nums1[m1]与nums2[m2 - 1]排除不可能区间循环条件left right终态left right即为答案位置边界兜底用float(-inf)/float(inf)处理空侧与越界情形复杂度达标$O(\log(mn))$ 时间、$O(1)$ 空间是本题区分「合格解」与「暴力归并解」的分水岭。5.2 同类题目延伸二分查找尤其是排除法写法在本仓库题目体系中是高频考点可继续延伸阅读基础二分查找0704. 二分查找二分边界类0034. 在排序数组中查找元素的第一个和最后一个位置排除法找边界的典型应用旋转数组二分0033. 搜索旋转排序数组、0153. 寻找旋转排序数组中的最小值二维有序结构二分0074. 搜索二维矩阵、0240. 搜索二维矩阵 II完整的二分查找题目列表见 docs/00_preface/00_06_categories_list.md。掌握本题的「第 k 小元素二分法」后再遇到任意变体如n个有序数组求中位数、数据流中位数时都能快速迁移这套分割与排除的思想。赞分享教程文档知识库【免费下载链接】AlgoNote⛽️「算法通关手册」从零开始的「算法与数据结构」学习教程200 道「算法面试热门题目」1000 道「LeetCode 题目解析」持续更新中项目地址https://gitcode.com/gh_mirrors/le/AlgoNote点击查看免费下载相关推荐LeetCode 0004 详解寻找两个正序数组的中位数Median of Two Sorted Arrays——从暴力归并到 O(log(min(n,m))) 最优二分LeetCode 0004 详解寻找两个正序数组的中位数Median of Two Sorted Arrays——从暴力归并到 O log min n,m示例工程教程leetcode 题解寻找两个正序数组的中位数——从暴力归并到 O(log(min(m, n))) 二分查找的完整拆解leetcode 题解寻找两个正序数组的中位数——从暴力归并到 O log min m, n 二分查找的完整拆解 本篇是 leetcode 题解仓库 prob文档教程知识库一文看懂 custom_macroHIVM 跨 Pipe 宏操作入门指南一文看懂 custom_macroHIVM 跨 Pipe 宏操作入门指南 在 AscendNPU IR 项目中 custom_macro 是 HIVM面向后端任务调度工作流自动化上一篇如何快速上手Mtab书签导航从安装到使用的完整指南下一篇项目总延期加人之前先看看这四个决策你做了没有——新人项目管理避坑创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表