资讯动态

LeetCode 4 二分查找法求解两个有序数组的中位数

发布时间:2026/10/21 9:16:16 来源:尧图企业网站定制
我最早刷到这个题的时候心里想的还是“合并两个数组取中间值”这种朴素方案。直到有一次面试被追问“能不能做到 O(log(mn)) 甚至 O(log(min(m,n)))”我才意识到 LeetCode 4 寻找两个正序数组的中位数 这道题真正的考点不在中位数本身而在“如何用二分法处理两个有序数组的划界问题”。LeetCode 4 作为一道高频面试题几乎每个大厂题库里都有它的位置但绝大多数解法讲得云里雾里上来就贴代码边界条件一团乱麻自己照着写一遍根本过不了。今天这篇我会把二分查找的核心推导、切分条件的来源、边界处理逻辑全部拆开讲清楚确保你读完能自己从零写出来而不是背题。这道题适合所有正在刷题准备面试的开发者也适合已经工作但想补一补算法基础的朋友。不管你是刚接触二分查找还是已经能写出暴力解但想优化到最优复杂度这篇文章都会给你一个完整、可复现的思考路径。1. 题目理解为什么中位数问题值得反复研究1.1 题目描述的完整拆解题目本身很好理解给定两个大小为 m 和 n 的正序从小到大数组 nums1 和 nums2要求找出并返回这两个正序数组的中位数。注意“正序”两个字这是整个算法能优化的基石如果没有这个前提任何二分思路都无从谈起。但真正把这道题推上“必刷题”位置的是最后那句算法的时间复杂度应该为 O(log (mn))。这个复杂度要求直接排除了合并排序 O(mn) 的做法。要知道在面试现场如果第一反应是开一个新的数组做归并面试官大概率会继续追问一句“能不能再快一点”。所以这道题表面考的是中位数实际考察的是你能否从“找中位数”这个具体问题里抽象出“第 k 小元素”的通用模型再结合二分法将复杂度降到对数级别。1.2 中位数定义的数学本质在动手写代码之前先要把中位数的定义吃透因为后续所有切分条件都是从这里推出来的。对于一个有序数组中位数满足一个关键性质它把数组分成左右两半使得左边元素的数量与右边元素的数量之差不超过 1并且左边所有元素都不大于右边所有元素。用数学语言说设数组长度为 nn 为奇数时中位数是第 (n1)/2 个元素。n 为偶数时中位数是第 n/2 个和第 n/21 个元素的平均值。这个定义看似简单但当你面对的是“两个数组合并后的中位数”时你就会发现你不能真的把两个数组合并因为那样复杂度就变成了 O(mn)。你要做的是直接在那个“虚拟的、合并后的数组”上找到正确的切分位置。怎么不合并却能知道切分位置这就是二分查找要解决的问题。本质上你是在两个数组里各找一个切分点使这两个切分点左边元素的数量之和等于“虚拟合并数组”总长度的前半部分。我个人的体会是一旦你把中位数理解为“一种满足左右数量平衡和大小顺序的切分结果”这道题就从一个“找数”的问题变成了一个“找切分点”的问题思路会一下子打开。1.3 这道题在面试中的考察权重从面试角度看LeetCode 4 是一道“讲了暴力解能拿及格分讲了 O(log(min(m,n))) 能拿高分”的题目。因为它的解法本身包含了好几个经典算法思想二分查找、分治、边界条件处理、数学建模。很多候选人能在白板上写出 O(mn) 的归并解法但你让他解释为什么切分条件是 L1 R2 L2 R1他就卡住了。另外这道题在第二轮的复杂度优化上还有一层隐含逻辑为什么要在较短的数组上二分这里面涉及时间复杂度的细节权衡。后面我会专门讲这里先记住结论——在短数组上二分复杂度是 O(log(min(m,n)))这就是最优解。2. 思路演进从合并排序到二分查找2.1 暴力方案合并排序 O(mn)最直观的思路是新开一个长度为 mn 的数组用双指针归并排序的方式把 nums1 和 nums2 合并然后直接取中间位置。这一步的代码逻辑很简单两个指针分别指向两个数组开头比较大小小的先放进新数组直到某个数组耗尽再把剩下的部分接上。合并完成后如果总长度是奇数中位数就是新数组第 (mn)//2 个元素如果总长度是偶数中位数就是第 (mn)//2 个和第 (mn)//2 - 1 个元素的平均值。这个方案没有任何错误但问题在于空间复杂度和时间复杂度都是 O(mn)。当两个数组各有一百万个元素时你需要额外分配两百万个元素的数组这在数据规模上来以后是不现实的。而且它完全没利用到“两个数组已经有序”这一条额外信息理论上可以利用指针移动而不用合并。如果你在现场只能想到这个方案先说清楚它的复杂度再补充一句“我可以继续优化”通常也能让面试官点头。但如果你连优化方向都没有分数就不会太高。2.2 等价转化寻找第 k 小元素 O(log(mn))在合并方案的基础上有一个经典的优化思路把“求中位数”转化为“求两个有序数组的第 k 小元素”。具体做法是写一个函数 findKth(nums1, nums2, k)每次比较两个数组第 k/2 个位置上的元素哪个小就排除哪个数组前 k/2 个元素然后 k 减去排除的个数递归或迭代继续。这样每轮 k 减半时间复杂度是 O(log(mn))。这个思路是正确的代码也不难写但它有一个坑它没有把“排除半个数组”的决策最优化。要知道你可以只排除较短的数组的一部分而不是总在两个数组上都操作一轮。这是后续 O(log(min(m,n))) 方案的核心出发点。2.3 最优方案在较短的数组上二分 O(log(min(m,n)))真正的最优解是在两个数组之间找一个全局的切分线用来模拟“虚拟合并数组”的左半边和右半边。这个切分线由两个切分点组成nums1 切在位置 inums2 切在位置 j。它需要满足两个条件左边的元素总数等于右边的元素总数或左边比右边多一个具体取决于总长度奇偶性。左边的最大值不大于右边的最小值。由于两个数组都升序只要满足上面的两个条件就可以确认合并数组的左半部分由 nums1[0..i-1] 和 nums2[0..j-1] 组成右半部分由 nums1[i..m-1] 和 nums2[j..n-1] 组成。然后如果总长度是奇数中位数就是左半部分的最后一个元素即 max(L1, L2)如果是偶数就是 (max(L1, L2) min(R1, R2)) / 2。这里最关键的是怎么找 i 和 j。由于总左半元素数量是固定的j 实际上由 i 唯一确定公式是j (mn1) / 2 - i。这意味着你只需要在一个变量上做二分。为了减少二分范围选择在较短的数组上二分移动 i 就能推算出 j。如果 nums1[i-1] nums2[j]说明 i 切大了左边多了 nums1 的元素需要把 i 左移。如果 nums2[j-1] nums1[i]说明 i 切小了左边少了 nums1 的元素需要把 i 右移。这个二分思路最优雅的地方在于它不关心每个数组各自的中点在哪里只关心“总左半部分”是否平衡并通过交叉比较来判断切分方向逐步逼近正确切分点。3. 核心细节切分条件的推导与边界处理3.1 为什么切分点只需要一个变量你把两个数组想象成一条整体的序列。合并后的总长度是 mn中位数要求左半部分元素个数为 totalLeft (mn1) / 2这个表达式无论奇偶都能保证左半部分不多于右半部分超过 1 个。问题在于这个“总左半”到底由 nums1 贡献多少、由 nums2 贡献多少假设 nums1 切在 i表示 nums1 的左半部分是 nums1[0..i-1]那么 nums2 的左半部分必须包含 totalLeft - i 个元素。换句话说j totalLeft - i。这个公式是硬性的因为它要保证元素总数固定。所以在整个二分过程中你只需要调整 ij 随之变化不需要同时调整两个变量。这就是“一个变量控制全局”的直觉来源。为了保证 i 的取值范围足够窄你可能要在较短的数组上做二分如果两个数组长度相差悬殊在长数组上二分会让 j 可能变成负数边界情况会更难处理。3.2 四个边界值的定义与含义在代码中你需要时刻维护四个值L1 nums1[i-1]nums1 左半部分的最大值如果 i0说明 nums1 全部属于右半部分此时 L1 为负无穷。R1 nums1[i]nums1 右半部分的最小值如果 im说明 nums1 全部属于左半部分此时 R1 为正无穷。L2 nums2[j-1]nums2 左半部分的最大值如果 j0L2 为负无穷。R2 nums2[j]nums2 右半部分的最小值如果 jnR2 为正无穷。注意负无穷和正无穷在这里不是真的设成 -inf 和 inf而是用语言里的哨兵值代替。在写代码时常用的做法是如果索引越界就返回 Integer.MIN_VALUE 或 Integer.MAX_VALUE。这样做的理由是如果左半部分没有元素那么左边最大值应该“非常小”永远不会成为那个 max(L1, L2)如果右半部分没有元素那么右边最小值应该“非常大”永远不会成为那个 min(R1, R2)。3.3 切分正确的判定条件交叉比较的直觉你已经知道正确切分要满足两个条件数量平衡i j totalLeft。顺序正确nums1 左半部分最大值 nums2 右半部分最小值同时 nums2 左半部分最大值 nums1 右半部分最小值。翻译成四个变量就是L1 R2L2 R1如果这两个条件同时成立恭喜你i 就是我们要的切分位置。如果条件不成立分两种情况L1 R2说明 nums1 左半部分里有一个元素比 nums2 右半部分的最小值还要大那这个元素无论如何都应该挪到 nums1 的右半部分去。也就是说 i 切多了需要减小 i。对应的二分搜索操作是 high i - 1。L2 R1说明 nums2 左半部分里有一个元素比 nums1 右半部分的最小值还要大那么这个元素应该挪到 nums2 的右半部分去而 nums2 的左半部分要减少意味着 j 要减小。又因为 j totalLeft - ij 减小等价于 i 增大。因此这里应该让 low i 1。这个“谁大谁往右挪”的直觉可以用生活场景类比想象你把一堆苹果nums1和一堆梨nums2混合在一起要放在两个筐里左边筐必须比右边筐要么相等要么多一个而且所有苹果和梨中小的往左、大的往右。如果发现左边筐里有苹果比右边筐里的梨还大那就说明这个苹果切错了边应该往右挪如果反之就说明梨切错了边该往左挪。由于 i 和 j 是绑定的挪一个变量就行。3.4 奇偶处理与中位数取值公式当 i、j 满足切分条件后中位数的取值分为总长度 mn 为奇数中位数 max(L1, L2)。因为左半部分恰好比右半部分多一个元素所以多出来的那一个就是左半部分的最后一个元素也就是整个合并数组的中点。总长度 mn 为偶数中位数 (max(L1, L2) min(R1, R2)) / 2。因为左半部分和右半部分数量相等中位数由最中间的两个元素决定一个是左半部分最大值一个是右半部分最小值。这个看似简单但很多人会在奇数情形的公式上踩坑。注意你不能在奇数情形直接用(L1 L2) / 2你必须取 max(L1, L2)。原因是 L1 和 L2 分别是两个数组左半部分的最大值合并后左半部分的“最大值”自然要取两者中较大的一个。3.5 为什么选择在短数组上二分这是很多人忽略的细节。如果两个数组长度分别是 m、n你在 nums1 上二分二分的复杂度是 O(log m)但切分点的正确性依赖于 j totalLeft - i 这个公式如果 m n你选的 i 变化范围很大j 很容易越界。更实际的问题是如果你想保证边界处理简单应该保证 i 对应的数组是较短的那个。因此代码开头通常加一步if len(nums1) len(nums2): nums1, nums2 nums2, nums1这样二分搜索只发生在较短的数组上复杂度从 O(log(mn)) 降到 O(log(min(m,n)))。这不只是理论上更优实际代码里边界处理也简单得多因为 j totalLeft - i 永远不会因为 i 过大而变成负数确保在较长的 nums2 上切出的 j 一定落在 [0, n] 范围内前提是 i 在 [0, m] 且 totalLeft 足够容纳。4. 代码实现二分查找的落地写法4.1 Python 完整实现与逐行解读下面给出我调试过多遍、最终稳定通过的实现注释尽量详细保证每一行都能对应到前面的推导def findMedianSortedArrays(nums1, nums2): # 保证 nums1 是较短的数组 if len(nums1) len(nums2): nums1, nums2 nums2, nums1 m, n len(nums1), len(nums2) total_left (m n 1) // 2 # 在 nums1 上二分切分点 i left, right 0, m while left right: # i 表示 nums1 左半部分包含的元素个数即切分位置 i (left right) // 2 # j 由总数约束推导 j total_left - i # 取出四个边界值注意越界时的哨兵值 nums1_left_max float(-inf) if i 0 else nums1[i - 1] nums1_right_min float(inf) if i m else nums1[i] nums2_left_max float(-inf) if j 0 else nums2[j - 1] nums2_right_min float(inf) if j n else nums2[j] if nums1_left_max nums2_right_min and nums2_left_max nums1_right_min: # 找到正确切分点 if (m n) % 2 1: return max(nums1_left_max, nums2_left_max) else: return (max(nums1_left_max, nums2_left_max) min(nums1_right_min, nums2_right_min)) / 2 elif nums1_left_max nums2_right_min: # i 切大了左移 right i - 1 else: # nums2_left_max nums1_right_mini 切小了右移 left i 1 # 正常情况下不会走到这里 return 0.04.2 关键点为什么循环条件是 left right这里我用的是闭区间二分。当 left right 时我们还要再判断一次当前 i 是否满足条件如果不满足再更新 left 或 right最终 left 会大于 right 退出循环。这个写法和标准的“在有序数组中查找目标值”的二分一致更容易推导正确性。如果你选择left right那么当某个边界情况发生时可能漏掉最后的检查。建议新手用left right不容易出死循环也更容易和二分模板对应。4.3 边界值哨兵的作用我见过不少同学在写这个题的时候一开始不处理 i0 或 im 的情况直接用 nums1[i-1] 和 nums1[i] 取数结果程序直接抛越界异常。处理方式有两个第一种像上面的代码在取值时用条件表达式判断越界并返回无穷值。第二种在数组两端虚拟地加上哨兵节点比如把 nums1 逻辑上看成 [-inf, ..., inf]但这样会影响数组长度计算代码会更绕。我建议用第一种。用无穷值的好处在于无论你如何调整 i切分条件里的比较都不会因为你取了越界值而出错。比如 i0 时nums1 左半部分没有元素但为了计算左边的最大值我们设定它是负无穷那它一定小于右边的任何数不会干扰判断。4.4 其他语言的适配注意如果你用的是 Java 或 C注意整数除法和浮点返回。Java 里(m n) % 2 1判断奇偶没问题但偶数情况求和时要用2.0而不是2否则整数相除会丢精度。C 里同理。另外Java 中可以用 Integer.MIN_VALUE 和 Integer.MAX_VALUE 作为哨兵但要注意不能直接用 int 表示无穷大参与后续的除法运算要先把它们转成 double 再计算。Python 因为支持大整数和 float(inf)就没这些烦恼写起来最顺畅。5. 如何验证切分是否“正确”可视化与实例走查5.1 手动实例两个数组都较长时假设 nums1 [1, 3, 8, 9, 15]nums2 [7, 11, 18, 19, 21, 25]m5n6总长度 11total_left 6。第一步因为 m n保持不变。初始 left0right5mid2所以 i2j6-24。nums1_left_max nums1[1] 3nums1_right_min nums1[2] 8nums2_left_max nums2[3] 19nums2_right_min nums2[4] 21检查条件3 21 成立19 8 不成立说明 nums2 左半部分的最大值太大切小了i 需要右移。于是 left3。第二步left3right5mid4i4j6-42。nums1_left_max nums1[3] 9nums1_right_min nums1[4] 15nums2_left_max nums2[1] 11nums2_right_min nums2[2] 18检查9 18 成立11 15 成立找到正确切分点。总长度奇数中位数 max(9, 11) 11。手动走查一遍你就能清晰看到二分是如何一步步“逼近”正确切分的而不是靠直觉盲目移动。5.2 手动实例两个数组有大量重复元素假设 nums1 [1, 1, 1, 1]nums2 [1, 1, 1, 1]mn4总长度 8total_left4。初始 i2j2。nums1_left_max 1nums1_right_min 1nums2_left_max 1nums2_right_min 1条件全部成立。偶数总长中位数 (max(1, 1) min(1, 1)) / 2 1。结果正确。这个例子提醒你切分条件的关键是不等式是否成立而不是边界值是否“不同”。即使左右值相等依然满足 L1 R2 和 L2 R1所以代码是安全的。5.3 特别关注某一个数组为空的情况如果 nums1 为空m0那么直接返回 nums2 的中位数即可。但在上面的统一算法里你并不需要单独写这个分支。因为 m0 时初始 left0right0i0jtotal_left-0 (0n1)//2正好是 nums2 左半部分的元素个数。然后四个边界值中 nums1 的两个全是无穷哨兵切分条件必然成立直接按公式返回。这就是统一解法的好处边界情况被自然吸收代码更简洁。我建议你先跑几个极端用例再提交nums1[], nums2[1]nums1[1], nums2[]nums1[], nums2[1,2]nums1[1,2], nums2[]。这些用例能把哨兵逻辑彻底验证清楚。6. 复杂度分析为什么能做到对数级6.1 时间复杂度的严谨推导整个算法只在较短的数组上进行二分每轮迭代 i 的范围减半。较短数组长度为 mm n所以二分次数是 O(log m)。因为 m n 整体可能非常大但 log m 严格不超过 log((mn)/2)所以 O(log(min(m,n))) 也是 O(log(mn)) 的下界。从实际对比来看当 m1n1_000_000 时暴力合并需要遍历百万级数据而归并找第 k 小可能需要 O(log(1_000_001)) 次但二分短数组只需常数次级别差距非常明显。每一次循环内部只做了常数次索引访问和比较没有额外的数组复制或排序。因此时间复杂度的上界不仅是 O(log(mn))而是更紧的 O(log(min(m,n)))。6.2 空间复杂度为什么是 O(1)代码中只用了有限个变量m、n、total_left、left、right、i、j以及四个边界值。没有新建数组也没有递归调用栈。因此空间复杂度是 O(1)也就是原地操作。这也让这个解法在嵌入式或内存受限的面试场景里显得更优雅。6.3 与其他解法的横向对比解法时间复杂度空间复杂度适用性合并后取中位数O(mn)O(mn)最直观适合小数据双指针不合并O(mn)O(1)比合并省空间但复杂度不变递归寻找第 k 小O(log(mn))O(1)思路清晰但边界较繁琐二分切分短数组O(log(min(m,n)))O(1)最优解推荐掌握6.4 复杂度优化的实际意义你可能觉得反正 O(mn) 在面试里也能过何必费劲写二分但如果这道题的输入是两个非常大的数组比如各一百万个元素O(mn) 意味着你要遍历两百万次而 O(log(min(m,n))) 只需要移动大约 20 次。在真实的数据处理场景里比如两个数据库表的有序主键求中位数或者两个日志文件的时间戳数组求中位数这种差距是数量级的。面试官要的不仅是你能解出题更是你有没有意识到规模增长后的性能差异。7. 常见问题与排查技巧实录7.1 问题一循环陷入死循环 或 left/right 不收敛最常发生在你把 right 更新成 i 而不是 i-1 的时候。如果你对二分模板不熟悉很容易在切大可切小时更新成 right i结果 i 永远不变形成死循环。解决办法是循环条件用 left right更新时严格用 right i - 1 和 left i 1。因为每次迭代要么 right 减小、要么 left 增大最终一定退出。另一个隐蔽的坑是 total_left 计算用的是(m n 1) // 2如果写成(m n) // 2奇偶情形下总数会差 1尤其是奇数总长度时左半部分元素数量不够导致 j 取到 n1越界访问。7.2 问题二越界访问无法正确处理哨兵如果你没有对 i 0 或 i m 做特殊处理直接靠数组访问一定会抛异常。一个常见的错误写法是nums1_left_max nums1[i - 1] nums1_right_min nums1[i]当 i0 或 im 时直接 IndexError。正确的思路是任何可能越界的访问都先判断越界则赋予无穷。在 Java/C 里尤其要注意数组下标不能为负。7.3 问题三奇数偶数判断错误导致结果不对总长度奇偶的判断应该是(m n) % 2 1这个没问题。但有人会把 total_left 的奇偶判断混在一起比如想通过 total_left 的奇偶性来判断中位数是取一个还是两个元素这是错误的。总长度奇偶才是决定合并数组中位数的唯一依据。total_left 只是左半部分的个数不代表合并数组的奇偶性。7.4 问题四误以为只需要满足 L1 R2很多简化版讲解只写了 nums1_left_max nums2_right_min漏掉了 nums2_left_max nums1_right_min。实际上两个条件必须同时满足否则左半部分中 nums2 的最大值可能大于右半部分中 nums1 的最小值合并顺序就被破坏了。你可以把elif nums1_left_max nums2_right_min和else两个分支分别对应了两个方向的调整漏掉任何一边都会导致二分方向错乱。7.5 问题五把递归实现改成迭代实现时出错递归找第 k 小的写法也存在但如果你把那一套逻辑硬套到“切分法”里容易混淆“排除前 k/2 个元素”和“在短数组上调整 i”的区别。切分法不是递归地想“删掉哪些元素”而是直接维护切分点的位置通过 i 的移动来逼近正确边界。我建议你把两种解法分开记一种是二分排除法用于找第 k 小一种是二分切分法用于本题最优解不要混着理解。7.6 实测记录与调试建议我在本地调试这道题时最常用的调试手段是在循环内部打印 i、j、L1、R1、L2、R2然后走查一个随机用例。比如i2, j4, L13, R18, L219, R221 i4, j2, L19, R115, L211, R218对比打印结果你能直观看到为什么切分方向会调整。等到送你上生产环境的算法题你不可能再用 print 调试所以强烈建议你现在就养成“手动构造小规模用例 打印中间状态 对照推导验证”的习惯。7.7 边界用例速查表输入场景期望结果重点检查点nums1[], nums2[1]1哨兵无穷值处理nums1[1], nums2[]1空数组交换逻辑nums1[1,2], nums2[3,4]2.5偶数中位数取平均nums1[1,3], nums2[2]2奇数中位数左半nums1[0,0], nums2[0,0]0重复元素相等判断nums1[], nums2[2,3,4,5]3.5单数组偶数长度8. 题目延伸一套思想解决多道题8.1 从“找中位数”到“找第 k 小元素”的抽象掌握了切分法之后你会发现它不只是解这一道题的工具。LeetCode 上还有一系列“两个有序数组合并求某种统计量”的题目本质都可以转成“找第 k 小元素”的二分模型。切分法的核心思想是用元素计数约束来替代实际合并用交叉比较来确定搜索方向。这个思想在“求两个有序数组的上四分位数”“求 K 个有序数组的中位数”等变体里同样适用。8.2 扩展到 K 个有序数组如果面试官再问“如果有 K 个有序数组怎么求中位数”切分法的思路就需要延伸K 个数组时需要同时确定 K 个切分点但计数约束只有一个因此无法像两个数组那样用一个变量推导所有变量。常见的做法是改用优先队列小顶堆来模拟第 k 小元素的选择或者用“多路归并 淘汰”的二分策略。这时候你会更深刻地理解为什么这道题只要求两个数组却如此经典——它恰好提供了“一个变量推导另一个变量”的数学结构这是不可多得的教学场景。8.3 实际工程场景在线分位数估算在真实的后端系统里分位数的计算通常不是一次性给你两个数组而是持续追加数据。比如某个监控系统要统计请求耗时的中位数日志源源不断写入你不可能每次新来一条日志就归并排序一次。常见的工程方案有用两个堆一个大根堆、一个小根堆维护数据流的中位数。用蓄水池抽样或 t-digest 等近似算法估算分位数。如果数据来自两个有序列且需要反复查询则可以用“二分固定切分点 局部调整”的思路做缓存。LeetCode 4 的切分思想其实很像这种“维护两个分区的边界”的思路你不需要关心所有元素只需要知道边界位置在哪里。这个思维方式在工程上的价值远超过刷题本身。8.4 从这道题到二分查找能力的整体提升说句掏心窝的话我见过很多开发者二分查找永远只会在“有序数组里找一个数”这种模板里打转。一旦遇到“两个数组”“不直接查找而是查找切分点”“通过计数间接推导位置”这类变体就手足无措。LeetCode 4 是打破这个局限的最好训练题。你一旦能完全独立推导出 L1/R1/L2/R2 这套逻辑再回头去做普通的二分模板题会发现自己对“搜索空间”和“单调性”的理解深了一个层次。我个人在实际刷题过程中每做完一遍这道题都会隔一两个月重新手写一次每次都会发现新的理解偏差。最近一次重写时我甚至重新审视了 total_left 公式讨厌而精妙的奇偶处理(mn1)//2不仅保证了左边元素数量正确而且天然让奇数长度下左边多一个偶数长度下左右相等。这种数学细节只有反复手推才能真正内化。最后再分享一个小技巧日常如果你在面试中遇到这道题即使你对最优解不能完全确定也可以先把“暴力合并 O(mn)”讲给面试官听然后再补充一句“这个思路是 O(mn)我有更优的方案可以在较短的数组上二分达到 O(log(min(m,n)))”。这一个补充就能让面试官看出你不仅会解题还有复杂度意识。实际面试里能清晰讲出切分条件的人已经能拿到良好以上的评价了。刷完之后建议你把这段推导过程写成自己的笔记过几周再手写一遍。我实践的体会是这种“隔段时间重写”的方式比连续刷十道同类题都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑