LeetCode 836矩形重叠:降维投影法,一行代码解决几何判断
2026/9/1 21:25:00 网站建设 项目流程

很多人在刷 LeetCode 时,看到“矩形重叠”这种题目,第一反应往往是:“这不就是简单的几何判断吗?直接比较坐标不就行了?” 然后兴冲冲地写下一堆if-else,结果要么漏掉边界情况,要么代码冗长到难以维护,最后在提交时才发现各种意想不到的测试用例。

力扣第 836 题“矩形重叠”就是这样一个典型的“简单题陷阱”。它表面上考察的是基础的二维几何知识,但真正要你掌握的,是一种降维打击的思维模型。如果你还在用“矩形A的四个角是否在矩形B内”这种思路去解题,那么这篇文章就是为你准备的。本文将带你跳出直觉陷阱,用最简洁、最优雅的 Python 数学逻辑,一击即中问题的核心。读完本文,你不仅能轻松解决这道题,更能学会一种处理区间重叠类问题的通用方法论,这在处理日程冲突、资源分配、碰撞检测等实际问题时,将让你事半功倍。

1. 这篇文章真正要解决的问题

我们首先要破除一个迷思:LeetCode 上的“简单”题,真的简单吗?对于第 836 题,其“简单”的标签往往让人轻视,导致陷入复杂的条件分支判断。实际上,这道题的核心价值在于,它强迫你从更高维度去抽象问题。

真正的问题:如何用最少的条件、最低的时间复杂度(O(1))和空间复杂度(O(1)),判断两个轴对齐矩形(即边平行于坐标轴的矩形)是否重叠。

为什么传统思路会失败?

  1. 条件冗余:检查一个矩形的角点是否在另一个矩形内部,需要检查4个点,每个点需要2个条件(x和y坐标范围),逻辑繁琐。
  2. 边界情况复杂:当矩形只是边接触(比如共享一条边)时,题目通常定义为“不重叠”。如何精确排除这种“相切”情况,需要非常小心的不等号处理(用>还是>=?)。
  3. 代码可读性差:一堆嵌套的if语句,不仅容易写错,几个月后自己都看不懂。

本文将解决的,正是如何绕过这些坑,直接抵达问题的数学本质:两个矩形不重叠的充要条件是什么?一旦想通了这一点,代码将变得异常简洁。这篇文章适合所有正在刷题、希望提升算法思维和代码简洁性的开发者,尤其是那些被各种边界条件折磨过的朋友。

2. 基础概念与核心原理

在深入代码之前,我们必须清晰定义问题中的几个关键概念,这是写出健壮代码的基础。

2.1 矩形在坐标系中的表示

在LeetCode本题中,一个矩形使用一个长度为4的整数列表[x1, y1, x2, y2]表示。

  • (x1, y1)是其左下角的坐标。
  • (x2, y2)是其右上角的坐标。
  • 这是一个轴对齐矩形,其四条边分别平行于x轴和y轴。
  • 保证x1 < x2y1 < y2

例如,矩形rec1 = [0, 0, 2, 3]表示一个左下角在原点,宽为2,高为3的矩形。

2.2 矩形“重叠”的定义

题目要求:如果两个矩形有正面积的公共区域,则称它们重叠。这意味着:

  • 仅边或角接触不算重叠。例如,一个矩形在另一个矩形的正上方且底边接触,不算重叠。
  • 必须有共同的内部点

2.3 核心原理:投影与分离轴定理的简化版

这是本文的核心判断。对于轴对齐矩形,判断是否重叠有一个极其高效的方法:分别检查它们在x轴和y轴上的投影区间是否都重叠

我们可以将二维的矩形重叠问题,分解为两个一维的区间重叠问题:

  1. X轴投影:矩形在x轴上的投影是一个区间[x1, x2]
  2. Y轴投影:矩形在y轴上的投影是一个区间[y1, y2]

关键结论:两个矩形重叠的充要条件是,它们在x轴上的投影区间并且在y轴上的投影区间同时重叠。

反之,两个矩形不重叠的充要条件是:它们在x轴上的投影区间或者在y轴上的投影区间不重叠

这个原理是解决本题的钥匙,它将一个二维空间的关系判断,简化为了两个独立的一维区间判断,复杂度大大降低。

3. 环境准备与前置条件

解决这道题几乎不需要特殊环境,但为了完整性和后续扩展,我们明确一下基础环境:

  • 编程语言:Python 3.x。本文所有代码示例均基于Python 3.6+。
  • 开发工具:任何文本编辑器或IDE均可(如VSCode、PyCharm、甚至LeetCode在线编辑器)。
  • 无需额外库:本题仅使用Python内置语法和运算符,无需安装任何第三方库。
  • 核心技能:理解列表索引、逻辑运算符(and,or,not)和比较运算符(<,<=,>>=)。

重点提醒:在编写判断逻辑时,请特别注意边界条件。题目要求“正面积”重叠,因此当区间“恰好相接”时(即一个区间的右端点等于另一个区间的左端点),应视为不重叠。这决定了我们使用><而不是>=<=

4. 核心流程拆解

让我们把“判断投影区间是否重叠”这个核心思想,拆解成可执行的步骤。

4.1 步骤一:提取投影区间

对于矩形rec = [x1, y1, x2, y2]

  • 其X轴投影区间为[x1, x2]
  • 其Y轴投影区间为[y1, y2]

我们需要处理两个矩形:rec1rec2

4.2 步骤二:判断一维区间是否重叠(核心中的核心)

如何判断两个一维区间[A_left, A_right][B_left, B_right]是否重叠(有公共长度)?重叠的条件是A_left < B_right并且B_left < A_right。 你可以这样理解:区间A的左端点在区间B的右端点左边,同时区间B的左端点也在区间A的右端点左边。这样两个区间必然有交集。

不重叠的条件(分离)A_left >= B_right或者B_left >= A_right。 即,区间A整体在区间B的右边,或者区间B整体在区间A的右边。

4.3 步骤三:应用二维判断

将步骤二应用于x轴和y轴:

  • X轴重叠条件:rec1[x1] < rec2[x2]rec2[x1] < rec1[x2]
  • Y轴重叠条件:rec1[y1] < rec2[y2]rec2[y1] < rec1[y2]

4.4 步骤四:得出最终结论

两个矩形重叠的最终条件是:X轴条件满足 并且 Y轴条件满足。 用代码表示就是:x_overlap and y_overlap

整个思考流程如下图所示(逻辑关系):

  1. 问题:二维矩形是否重叠?
  2. 降维:分解为X轴和Y轴两个一维区间是否重叠?
  3. 判断一维区间:是否满足left_A < right_B and left_B < right_A
  4. 综合:两个维度的判断结果取逻辑与(and)。

5. 完整示例与代码实现

理解了原理,代码实现就水到渠成。我们将从最直观的写法开始,逐步优化到最简洁优雅的形式。

5.1 版本一:清晰易懂版

这个版本将每一步逻辑都清晰展示,非常适合理解。

def isRectangleOverlap(rec1, rec2): """ 判断两个轴对齐矩形是否重叠。 :type rec1: List[int] :type rec2: List[int] :rtype: bool """ # 解包矩形坐标,增加可读性 rec1_x1, rec1_y1, rec1_x2, rec1_y2 = rec1 rec2_x1, rec2_y1, rec2_x2, rec2_y2 = rec2 # 判断在x轴上的投影是否重叠 # 重叠条件:rec1的左边界 < rec2的右边界, 且 rec2的左边界 < rec1的右边界 x_overlap = rec1_x1 < rec2_x2 and rec2_x1 < rec1_x2 # 判断在y轴上的投影是否重叠 # 重叠条件:rec1的下边界 < rec2的上边界, 且 rec2的下边界 < rec1的上边界 y_overlap = rec1_y1 < rec2_y2 and rec2_y1 < rec1_y2 # 两个方向都重叠,矩形才重叠 return x_overlap and y_overlap # 测试用例 if __name__ == "__main__": # 用例1:重叠 rec1 = [0, 0, 2, 2] rec2 = [1, 1, 3, 3] print(f"矩形{rec1}和{rec2}是否重叠? {isRectangleOverlap(rec1, rec2)}") # 应输出 True # 用例2:不重叠(x轴分离) rec1 = [0, 0, 1, 1] rec2 = [2, 0, 3, 1] print(f"矩形{rec1}和{rec2}是否重叠? {isRectangleOverlap(rec1, rec2)}") # 应输出 False # 用例3:不重叠(y轴分离) rec1 = [0, 0, 1, 1] rec2 = [0, 2, 1, 3] print(f"矩形{rec1}和{rec2}是否重叠? {isRectangleOverlap(rec1, rec2)}") # 应输出 False # 用例4:边接触(应返回False) rec1 = [0, 0, 1, 1] rec2 = [1, 0, 2, 1] print(f"矩形{rec1}和{rec2}是否重叠? {isRectangleOverlap(rec1, rec2)}") # 应输出 False

代码逻辑解释

  • x_overlap = rec1_x1 < rec2_x2 and rec2_x1 < rec1_x2:这是区间重叠判断的直接翻译。注意是严格小于(<),确保了边接触(rec1_x2 == rec2_x1)时返回False
  • y_overlap同理。
  • 最终返回x_overlap and y_overlap,要求两个方向必须同时重叠。

5.2 版本二:简洁一行版(面试常用)

在理解原理后,可以写出非常简洁的代码,这在面试中能体现你的思维清晰度。

def isRectangleOverlap_concise(rec1, rec2): """ 简洁的一行版本。 核心逻辑:判断不重叠的条件,然后取反。 """ # 如果矩形1在矩形2的左侧、右侧、下方、上方,则不重叠。 # 注意:由于矩形用左下和右上表示,‘在左侧’意味着 rec1_x2 <= rec2_x1 # 我们直接判断重叠的条件,即‘不在左侧、不在右侧、不在下方、不在上方’ return not (rec1[2] <= rec2[0] or # rec1在rec2左侧 rec1[0] >= rec2[2] or # rec1在rec2右侧 rec1[3] <= rec2[1] or # rec1在rec2下方 rec1[1] >= rec2[3]) # rec1在rec2上方 # 更Pythonic的写法,直接使用投影判断 def isRectangleOverlap_oneline(rec1, rec2): """ 最经典和优雅的一行版本,直接使用投影重叠条件。 """ return rec1[0] < rec2[2] and rec2[0] < rec1[2] and rec1[1] < rec2[3] and rec2[1] < rec1[3]

版本对比

  • isRectangleOverlap_concise:从“不重叠”的角度思考,代码表达了矩形分离的四种情况。逻辑清晰,但可读性稍逊。
  • isRectangleOverlap_oneline这是推荐掌握的最优写法。它直接、正面地表达了重叠的四个必要条件,没有任何冗余,且效率最高。

5.3 版本三:面向对象版(拓展思维)

如果你在做一个图形项目,可以将矩形抽象成类,使代码更模块化。

class Rectangle: def __init__(self, x1, y1, x2, y2): """初始化矩形,确保是有效的轴对齐矩形。""" if x1 >= x2 or y1 >= y2: raise ValueError("Invalid rectangle coordinates. Must satisfy x1 < x2 and y1 < y2.") self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 def overlaps_with(self, other): """判断当前矩形是否与另一个矩形重叠。""" # 使用经典的一行逻辑 return (self.x1 < other.x2 and other.x1 < self.x2 and self.y1 < other.y2 and other.y1 < self.y2) @staticmethod def from_list(coord_list): """从列表 [x1, y1, x2, y2] 创建矩形对象。""" return Rectangle(*coord_list) # 使用示例 if __name__ == "__main__": rec1_obj = Rectangle.from_list([0, 0, 2, 2]) rec2_obj = Rectangle.from_list([1, 1, 3, 3]) rec3_obj = Rectangle.from_list([5, 5, 6, 6]) print(f"rec1 与 rec2 重叠: {rec1_obj.overlaps_with(rec2_obj)}") # True print(f"rec1 与 rec3 重叠: {rec1_obj.overlaps_with(rec3_obj)}") # False

这个版本虽然对本题来说“杀鸡用牛刀”,但它展示了如何将算法思想封装成可复用的组件,在实际工程项目中更有价值。

6. 运行结果与效果验证

我们使用LeetCode官方的测试用例来验证我们代码的正确性。你可以将isRectangleOverlap_oneline函数直接提交到力扣第836题。

如何验证你的代码?

  1. 基础功能测试:使用上面代码中的几个简单用例,确保能正确区分重叠与不重叠。
  2. 边界条件测试:这是关键。重点测试“边接触”和“角接触”的情况,确保返回False
    # 测试边界条件 test_cases = [ # (rec1, rec2, expected_result, description) ([0,0,1,1], [1,0,2,1], False, "右边接触"), ([0,0,1,1], [0,1,1,2], False, "上边接触"), ([0,0,1,1], [-1,0,0,1], False, "左边接触"), ([0,0,1,1], [0,-1,1,0], False, "下边接触"), ([0,0,2,2], [1,1,3,3], True, "部分重叠"), ([0,0,1,1], [2,2,3,3], False, "完全分离"), ([0,0,3,3], [1,1,2,2], True, "包含"), ] for rec1, rec2, expected, desc in test_cases: result = isRectangleOverlap_oneline(rec1, rec2) status = "✓" if result == expected else "✗" print(f"{status} {desc}: rec1={rec1}, rec2={rec2}, 预期={expected}, 实际={result}")
  3. 在LeetCode上提交:最终极的验证。将函数复制到LeetCode的代码编辑器中,点击“执行代码”查看是否通过所有测试用例,然后“提交”看是否通过。

预期输出: 对于上述边界测试,你应该看到所有测试用例前都是。如果出现,请仔细检查你的比较运算符(必须是<>,不能是<=>=)。

7. 常见问题与排查思路

即使理解了原理,在实现时也可能遇到一些典型问题。下表总结了常见错误和解决方案:

问题现象可能原因排查方式解决方案
边接触的矩形被判断为重叠在判断条件中使用了<=>=检查代码中的比较运算符。题目要求“正面积”重叠,边接触不算。将所有判断重叠的条件中的<=改为<。例如rec1_x1 <= rec2_x2改为rec1_x1 < rec2_x2
完全包含的矩形被判断为不重叠逻辑判断顺序错误或使用了“或”逻辑检查最终返回语句。重叠需要X和Y同时满足条件。确保返回语句是return x_cond and y_cond,而不是or
索引错误(IndexError)输入的矩形列表长度不为4在函数开头添加输入验证。添加断言或条件判断:assert len(rec1) == 4 and len(rec2) == 4
代码对某些用例正确,对另一些错误坐标赋值错误,混淆了x1, y1, x2, y2的顺序使用有意义的变量名解包,而不是直接使用rec1[0]采用x1, y1, x2, y2 = rec1的解包方式,提高可读性,避免索引混淆。
认为“一个角在内部”就是重叠理解偏差,忽略了矩形可以相交但角点都不在对方内部的情况画图分析。两个矩形十字交叉时,可能没有任何一个角点在对方内部,但它们确实重叠。回归核心原理:必须用投影区间法判断,这是唯一可靠的方法。

一个高级的思维陷阱:有同学会想“先判断不重叠的情况是不是更简单”?比如,矩形1在矩形2的左边、右边、上边、下边。这思路是对的(如我们的简洁版2),但必须注意边界。“在左边”的条件是rec1_x2 <= rec2_x1(允许边接触),然后对四种分离情况取“或”。最后对整体结果取“非”,得到是否重叠。这种“判断不重叠”的思路和“判断重叠”的思路是等价的,但更容易在边界条件上出错,所以更推荐正面判断的“一行版本”。

8. 最佳实践与工程建议

将这道题的解决方案融入更广泛的工程和刷题实践中,你可以做得更好。

8.1 刷题最佳实践

  1. 先画图,再编码:对于几何问题,在纸上或白板上画出各种情况(重叠、分离、包含、边接触),直观理解条件。
  2. 从暴力法思考,再优化:即使一眼就知道最优解,也可以先想想暴力法(比如比较所有点),这能帮你理清所有边界情况,然后再寻找数学规律进行优化。
  3. 测试用例驱动:不要只依赖题目给的例子。自己设计测试用例,特别是:
    • 极端情况(坐标很大或很小)。
    • 边界情况(边接触、角接触)。
    • 对称情况(交换两个矩形输入,结果应不变)。
  4. 掌握“投影降维”思想:这是本题最重要的收获。许多高维问题可以分解为低维问题的组合。例如,判断三维长方体是否重叠,可以分解为判断x, y, z三个轴上的投影区间是否都重叠。

8.2 代码风格与性能

  1. 追求简洁,而非晦涩isRectangleOverlap_oneline版本很简洁,但在团队项目中,如果算法不是众所周知的,建议添加一行注释说明原理,如# 检查x轴和y轴投影是否均重叠
  2. 时间复杂度与空间复杂度:本解法时间和空间复杂度都是 O(1),已是理论最优。无需进一步优化。
  3. 防御性编程:在生产代码中,应考虑输入验证。虽然LeetCode保证输入有效,但实际工程中需要处理无效矩形(如x1 > x2)或空输入。
    def isRectangleOverlap_robust(rec1, rec2): # 输入验证 if not rec1 or not rec2 or len(rec1) != 4 or len(rec2) != 4: return False # 或抛出异常 # 验证是否为有效矩形(左下角坐标小于右上角) if not (rec1[0] < rec1[2] and rec1[1] < rec1[3] and rec2[0] < rec2[2] and rec2[1] < rec2[3]): return False # 无效矩形,按题目定义可能不会出现,但工程中要处理 # 核心逻辑 return rec1[0] < rec2[2] and rec2[0] < rec1[2] and rec1[1] < rec2[3] and rec2[1] < rec1[3]

8.3 扩展到实际问题

“区间重叠”判断是一个基础算法组件,应用场景极广:

  • 日程安排:判断两个会议时间段是否冲突。
  • 游戏开发:2D游戏中精灵的碰撞检测(轴对齐包围盒)。
  • 数据库查询:判断两个时间段是否有交集的SQL查询。
  • 资源分配:检查设备使用时间是否重叠。

例如,判断两个会议[start1, end1][start2, end2]是否冲突的代码,与本题的X轴判断逻辑完全一致:

def is_meeting_conflict(meeting1, meeting2): """判断两个会议时间是否重叠。""" start1, end1 = meeting1 start2, end2 = meeting2 # 会议重叠的条件:一个会议的开始时间早于另一个会议的结束时间,并且反之亦然。 # 注意:一个会议在另一会议结束时立刻开始,不算冲突,所以用 `<`。 return start1 < end2 and start2 < end1

9. 总结与后续学习方向

力扣第836题“矩形重叠”是一道经典的“思维转换”题。它教会我们的,远不止如何比较几个坐标。其核心价值在于降维思想对问题本质的抽象。通过将二维重叠问题分解为两个一维区间问题,我们得到了一个时间复杂度O(1)、空间复杂度O(1)的优雅解法,代码仅需一行。

本文的核心收获

  1. 不要被“简单”标签迷惑:深入理解题目定义(正面积重叠,边接触不算)。
  2. 掌握投影判断法:这是解决轴对齐矩形重叠最高效、最不易出错的方法。
  3. 警惕边界条件:严格使用<>来排除边接触情况。
  4. 代码的优雅在于本质的洞察:最简洁的return rec1[0] < rec2[2] and rec2[0] < rec1[2] and rec1[1] < rec2[3] and rec2[1] < rec1[3]是建立在对问题深刻理解之上的。

后续可以如何深入?

  1. 挑战升级:尝试解决LeetCode 223题“矩形面积”,它需要你在判断重叠的基础上,计算两个矩形覆盖的总面积。
  2. 维度升级:思考如何判断三维空间中的轴对齐长方体是否重叠?原理完全一致,只需增加Z轴的判断。
  3. 算法扩展:如果矩形不是轴对齐的(即旋转矩形),如何判断重叠?这需要更复杂的几何知识,如分离轴定理(Separating Axis Theorem, SAT),这是游戏物理引擎中常用的算法。
  4. 实战应用:在你的下一个个人项目中,如果需要用到碰撞检测或时间调度,尝试自己实现这个重叠判断函数,体会从算法题到实际应用的转换。

刷题的目的,不仅是写出能通过测试的代码,更是训练一种化繁为简、直击要害的思维能力。矩形重叠这道题,就是一个完美的起点。建议你将文中的“一行解法”和其背后的投影思想牢记于心,它将成为你算法工具箱中一件锋利而趁手的武器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询