从我自己开始按系列整理刷题笔记以来,基本每篇都是靠一道具体题目带出一个或几个可复用知识点。这期写 LeetCode 知识点总结,轮到第 223 题 Rectangle Area,也就是“矩形面积”。说实话,这道题本身真的不难,很多人可能在 LeetCode 上花五分钟就把它划走了。但它恰恰是我最愿意拿出来做总结的那类题目:表面上是小学数学几何题,实际上把一维区间相交、容斥原理、边界处理、整数溢出这几个在面试里很容易聊出细节的点全都串到了一起。所以这篇既是在讲 223 题怎么做,更是在讲以后遇到任何“求两个区间重叠部分”的题,你应该怎么拆、怎么写、怎么给别人讲明白。
适合看这篇的读者也不用卡门槛。你只要知道矩形左下角、右上角坐标是什么意思,能读懂简单代码,剩下的推导我会一步步补全。文章后面还扩展到了 LeetCode 热门 100 题里常见的区间题、以及把两个矩形扩展成 N 个矩形时的处理思路,哪怕你目标是周赛也能有点收获。
1. 题目拆解:先搞清楚 223 题到底在问什么
1.1 原题描述和一次手算模拟
223 题的题目描述并不长。平面上有两个矩形,它们都是“轴对齐矩形”,也就是每条边都和 x 轴或 y 轴平行,不会出现斜着放的矩形。第一个矩形用左下角坐标和右上角坐标表示,记作矩形 A:(ax1, ay1, ax2, ay2)。第二个矩形同理,记作矩形 B:(bx1, by1, bx2, by2)。题目要求返回这两个矩形共同覆盖的总面积。
这里的用词很关键:不是让你返回“两个矩形的面积之和”,而是“覆盖的总面积”。如果两个矩形完全不挨着,那总面积确实等于各自面积相加。但只要有一小块重叠区域,直接相加就会把重叠部分算两遍,答案就会偏大。所以这道题要处理的核心只有一件事:判断两个矩形是否重叠,如果重叠了,算出重叠面积,然后在两个矩形面积之和中减掉它。
官方样例也比较典型:矩形 A 左下角是 (-3, 0),右上角是 (3, 4),所以 A 的宽度是 6,高度是 4,面积 24。矩形 B 左下角是 (0, -1),右上角是 (9, 2),宽度是 9,高度是 3,面积 27。两个矩形面积相加是 51。再看重叠:x 方向上,A 的范围是 [-3, 3],B 的范围是 [0, 9],共同覆盖 [0, 3],长度为 3;y 方向上,A 的范围是 [0, 4],B 的范围是 [-1, 2],共同覆盖 [0, 2],高度为 2。于是重叠区域是一个宽 3 高 2 的矩形,面积 6。最终答案就是 51 - 6 = 45。
1.2 隐藏的考点不是几何题
看完样例,可能会有读者觉得这题太基础了。但实际刷题和面试里,这题的隐藏考点很值得展开:
第一,两个矩形是否重叠,可以被拆成两条坐标轴上“一维线段是否有正长度交集”来判断。矩形在 x 轴上的投影是一条线段,在 y 轴上的投影也是一条线段。只有当两个投影在 x 方向有交、在 y 方向也有交时,二维矩形才有真正的重叠面积。
第二,计算“总面积等于两个面积相加减交集面积”,是最朴素的容斥原理。虽然这题只有两个集合,但背后那种“别重复计数”的思想在很多组合题里都会出现。
第三,代码写到后面还有类型的细节问题。坐标可能是负数,减法结果可能为负,乘积可能超过 int 范围,这些如果不提前考虑,本地跑样例没问题,一提交可能就出现非常隐蔽的错。
还有一点我可以提前说:223 题本身不算 LeetCode 热门 100 题里的高频原题,但它代表的“区间相交”能力,确实高频出现在面试题里。你把这题的数学关系理清楚,再去碰会议安排、区间合并、区间交集这类问题会轻松很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 关键推导:从一维线段相交到二维矩形重叠
2.1 线段相交公式才是整道题的根基
我想先把二维问题降维成一维问题看。比方说,x 轴上有两条线段,第一条是 [a, b],第二条是 [c, d],这里我们假设 b > a、d > c。怎么算两条线段重叠部分的长度?
很多人的第一反应是分情况讨论:A 完全在 B 左边,A 完全在 B 右边,A 包含 B,B 包含 A,部分相交……这种分类当然能算对,但一旦坐标变成四个变量,分情况讨论很容易漏掉边界。更好的办法是记住一个统一公式:
重叠长度 = min(右端点) - max(左端点)
写成变量就是:
overlap = min(b, d) - max(a, c)
这个公式怎么理解?两条线段“共同覆盖”的区域,左边界必然是更靠右的那个左端点,也就是 max(a, c);右边界必然是更靠左的那个右端点,也就是 min(b, d)。右边减去左边,自然就是共同覆盖的长度。
但这个结果可能是负数。比如线段 A 是 [0, 1],线段 B 是 [5, 8],那么 min(b, d) = min(1, 8) = 1,max(a, c) = max(0, 5) = 5,算出来是 1 - 5 = -4。负数意味着两条线段在 x 轴上中间隔着一段空隙,根本没有交。所以正确取值应该对 0 取 max:
overlap = max(0, min(b, d) - max(a, c))
这样,不相交时结果是 0,部分重叠或完全包含时得到正数结果,刚好贴合“面积是零或正数”的实际含义。
2.2 把一维公式沿着 x 轴和 y 轴各用一次
回到二维平面上的两个矩形。因为矩形是轴对齐的,矩形在 x 方向的“覆盖范围”就是 [ax1, ax2] 和 [bx1, bx2] 这两条线段,在 y 方向的“覆盖范围”就是 [ay1, ay2] 和 [by1, by2] 这两条线段。
于是重叠矩形的边界可以直接算出来:
重叠矩形的左边界 x_left = max(ax1, bx1)
重叠矩形的右边界 x_right = min(ax2, bx2)
重叠矩形的下边界 y_bottom = max(ay1, by1)
重叠矩形的上边界 y_top = min(ay2, by2)
只要 x_left < x_right 并且 y_bottom < y_top,两个矩形就有实实在在的重叠区域。重叠区域的宽度是 x_right - x_left,高度是 y_top - y_bottom,面积就是宽度乘高度。
这里有个值得强调的细节:x 方向和 y 方向是独立的。这看起来像废话,但很多人会把二维重叠看成某种“整体包含关系”,试图用坐标直接判断 A 的右上角是否在 B 的左下角和右上角之间之类的方法。那种判断也能做,但容易遗漏。而先分别算两个方向的一维重叠,再取交集,逻辑不容易出错。
为了不让负值参与后面的判断,更严谨的写法是:
overlap_w = max(0, min(ax2, bx2) - max(ax1, bx1))
overlap_h = max(0, min(ay2, by2) - max(ay1, by1))
这样哪怕两个矩形在 x 方向上根本不碰头,overlap_w 也会是 0,和 overlap_h 相乘后重叠面积仍然是 0,不会出现负数面积。
2.3 两个集合的并集和容斥原理
现在最后一步就顺理成章了。矩形 A 实际覆盖的面积为 areaA,矩形 B 实际覆盖的面积为 areaB。如果我们直接把 areaA + areaB,重叠区域 O 会被计算两次。但“覆盖总面积”指的是至少被一个矩形覆盖到的区域,重叠区域只能算一次。
于是有:
总面积 = areaA + areaB - O
这就是两个集合的容斥原理。如果两个矩形完全没有重叠,O = 0,公式仍然成立。如果其中一个矩形被另一个完全包住,比如 A 完全在 B 里,那 O 就等于 areaA,总面积就是 areaB,也很自然。
我在给别人讲这道题的时候,还会用一个直观例子帮助理解容斥:你有一堆红色方块和一堆蓝色方块,想知道两种颜色覆盖到的格子总数。把红色和蓝色各自数一遍,中间同时被红蓝涂过的格子就会被数两次,所以要减掉一次。拿矩形面积去套,道理完全相同。
3. 三种代码写法与最终实现
3.1 结构最清晰的 Python 写法
如果你用的是 Python,我推荐直接按“两个面积相加减重叠面积”的自然思路写,代码几乎可以和公式一一对应:
python复制class Solution:
def computeArea(self, ax1: int, ay1: int, ax2: int, ay2: int,
bx1: int, by1: int, bx2: int, by2: int) -> int:
area_a = (ax2 - ax1) * (ay2 - ay1)
area_b = (bx2 - bx1) * (by2 - by1)
overlap_w = max(0, min(ax2, bx2) - max(ax1, bx1))
overlap_h = max(0, min(ay2, by2) - max(ay1, by1))
return area_a + area_b - overlap_w * overlap_h
这段代码里没有 if,也没有分情况讨论。max(0, ...) 这一步把“x 方向不相交”的情况自动归零,overlap_w 和 overlap_h 里只要有一个是 0,重叠面积就是 0,最后由减法不会扣除错误内容。
这种写法的好处是逻辑完整、便于讲解。面试的时候你甚至可以一边写一边说:第一步算两个矩形的面积,第二步算两个方向上的重叠长度,第三步用容斥得到结果。整个复杂度是 O(1),只用常数个变量,空间复杂度也是 O(1)。
3.2 C++ 实现要注意整数乘法溢出
在 C++ 或 Java 里写这道题,同一个公式但要多留一个心眼:int 和 int 直接相乘,结果还是 int,如果乘积超越了 int 能表示的范围,就会溢出成负数,最后答案完全错乱。
虽然这道题的原题数据范围通常不会真的让你溢出,但作为解题习惯,我会选择用 long long 参与乘法。坐标先用 int 存没有问题,因为坐标之间的差值本身不会大得离谱,但一旦涉及乘法,就先把其中一个因子转成 long long。
cpp复制class Solution {
public:
int computeArea(int ax1, int ay1, int ax2, int ay2,
int bx1, int by1, int bx2, int by2) {
long long areaA = 1LL * (ax2 - ax1) * (ay2 - ay1);
long long areaB = 1LL * (bx2 - bx1) * (by2 - by1);
long long overlapW = max(0, min(ax2, bx2) - max(ax1, bx1));
long long overlapH = max(0, min(ay2, by2) - max(ay1, by1));
return static_cast<int>(areaA + areaB - overlapW * overlapH);
}
};
1LL 的作用是先把后面的整数表达式提升成 long long,再执行乘法,这样面积计算过程就不会发生 int 溢出。overlapW 和 overlapH 声明成 long long 则是为了让后面的乘积也在 long long 范围内计算。
实际用 C++ 刷题时,我建议你写完后顺手把样例里的坐标故意调大一些,比如把多个坐标设成 10^9 级别,跑一下看看是否出现负数结果。这种“用极端输入验证”的测试习惯,考试和面试中对避免溢出类问题都很有效。
3.3 先求重叠矩形坐标的等价写法
另一种常见写法是先把重叠矩形的四个边界求出来,再判断边界是否形成有效矩形。本质上和前面的公式等价,但代码阅读性也不错:
python复制class Solution:
def computeArea(self, ax1: int, ay1: int, ax2: int, ay2: int,
bx1: int, by1: int, bx2: int, by2: int) -> int:
area_a = (ax2 - ax1) * (ay2 - ay1)
area_b = (bx2 - bx1) * (by2 - by1)
x1 = max(ax1, bx1)
y1 = max(ay1, by1)
x2 = min(ax2, bx2)
y2 = min(ay2, by2)
overlap = 0
if x1 < x2 and y1 < y2:
overlap = (x2 - x1) * (y2 - y1)
return area_a + area_b - overlap
这种写法的好处是“重叠矩形”这个概念被显式建模出来了。如果在面试现场聊到后续扩展,比如面试官突然问你“那如何返回重叠矩形的坐标”,你直接把 x1、y1、x2、y2 返回就行。两种写法没有高下之分,第一种更简洁,第二种更贴近几何建模思路,你选一种自己不容易写错的方式固定下来即可。
4. 真正容易写错的地方与排查方法
4.1 没有对负数重叠长度做处理
这是这道题最典型的错误之一。很多人算出 overlap_w = min(ax2, bx2) - max(ax1, bx1) 后,直接拿这个值去乘 overlap_h,完全忘了两个矩形可能在 x 方向上根本没有交集,此时 overlap_w 是个负数。
一旦用负数去乘正数,得到负的重叠面积,再被 areaA + areaB 减掉,结果反而比两个矩形面积之和还大。我在给代码做 code review 的时候,遇到这种写法就会特别提醒:计算长度、面积这类几何量之前,先想想这个量是否可能是负数,如果可能,要么用 max(0, x) 约束,要么用 if 判断,不要让负值进入后续公式。
4.2 边界相切时不小心多算面积
看题的人容易忽略“边界贴在一起”的情况。假设矩形 A 的右边界 x = ax2 正好等于矩形 B 的左边界 bx1,这时重叠宽度是 0,两个矩形只是共用一条边,面积交叠是 0,总面积应该是两个面积之和。
如果你用条件 x1 <= x2 来判断是否有重叠,可能在 x1 == x2 时也会进入重叠计算逻辑,但由于宽度为 0,相乘结果仍然是 0。真正的风险不在于等于号本身,而在于如果代码里同时允许 x1 <= x2 和 y1 <= y2 判断,又在另一个分支手滑用了负长度减掉某个负值,结果就很难排查。我的习惯是统一写成严格小于,重叠矩形必须同时满足左边界小于右边界、下边界小于上边界,才认为存在正面积重叠。
4.3 减重叠面积时减错方向
有些解法会在 x、y 两个方向分别判断,比如先算出 overlap_w,再算出 overlap_h,然后返回 areaA + areaB - overlap_w * overlap_h。这本身没问题。但我见过不少错误版本,在计算重叠面积时只乘了其中一个方向的长度,比如用 overlap_w * 1,或者把 overlap_w 和 overlap_h 相加再减,导致最终答案明显偏离。
我建议你把这个容易错的地方记成一句口诀:二维矩形重叠区域的面积,是一维重叠宽乘一维重叠高。先用两个方向上的长度验证重叠区域确实存在,再用两数相乘。如果算出来重叠面积比两个矩形中最小的那个还大,就要立刻怀疑是不是乘错了维度。
4.4 写用例时漏掉“完全不相交”和“包含”情况
我刷这道题时会给自己列一组必测输入:两个矩形相距很远、两个矩形左右相邻只碰一条边、两个矩形上下重叠但左右不重叠、一个矩形完全被另一个包含、两个矩形完全相同、其中一个矩形的某条边落在另一个矩形中间。很多自认为写对的人,其实只测了样例里那种“部分重叠”情况,结果一到边界测试就出问题。
把这些用例列出表格会直观一些:
| 场景 | 重叠宽度 | 重叠高度 | 期望行为 |
|---|---|---|---|
| 完全不相交 | 0 | 0 | 返回两个面积之和 |
| 左右边界相切 | 0 | 正数 | 返回两个面积之和 |
| 上下方向不重叠 | 正数 | 0 | 返回两个面积之和 |
| A 完全包含 B | 等于 B 的宽度 | 等于 B 的高度 | 返回 A 的面积 |
| 完全相同 | 整个宽度 | 整个高度 | 返回其中一个矩形面积 |
把这几条输入跑一遍,基本可以把这道题的边界覆盖完,也能有效检验你写的是不是通用公式,而不是只针对某个特殊布局硬算出来的答案。
5. 放在 LeetCode 刷题体系里看,这道题能扩展出什么
5.1 从两个矩形扩展到 N 个矩形的覆盖总面积
我在刷题群里经常被问到:既然两个矩形可以这样做,如果是 N 个矩形求覆盖总面积呢?答案是 O(1) 公式不再够用,核心思路要升级成“扫描线”。
扫描线的大致流程是:把所有矩形的左右两条竖直边拆成“事件”,左边界事件表示在某个 x 区间加入一条 y 方向线段,右边界事件表示移除一条 y 方向线段。然后把所有事件按 x 坐标排序,从左往右扫描。在相邻两个事件之间,竖直方向的覆盖集合没有变化,于是可以算出这一小段的宽度乘上当前被覆盖的 y 方向长度,累加每个小段的面积,最后得到总面积。
比如矩形的左边界是 (x1, y1, y2),右边界是 (x2, y1, y2)。排序扫描时,先遇到所有矩形的左边界就把 y 区间插入一个集合,遇到右边界就把对应 y 区间删除。当前被覆盖的 y 总长度乘以当前 x 事件和上一个 x 事件的差值,就是这一个水平窄条覆盖的面积。
扫描线的实现细节比 223 题复杂不少,尤其是维护 y 方向覆盖长度,既可以用线段树,也可以用有序结构配合区间合并。如果你的目标只是把面试基础题打牢,223 题先建立一个“投影分解”的直觉,后面学扫描线会容易很多。实际上 LeetCode 里也有专门考 N 个矩形覆盖面积的题,比如第 850 题 Rectangle Area II,用的就是这个思路。
5.2 用一张题单把“区间相交”同类题串起来
这道题真正该记住的是一个核心模型:在一条直线上,两个区间的交集长度 = max(0, min(右端点) - max(左端点))。这个模型在不少题里换着马甲出现。
举个例子,LeetCode 第 56 题“合并区间”是在求多个区间的并集,需要先按左端点排序,再检查当前区间和上一个合并结果是否有重叠,有就扩展右端点。第 986 题“区间列表的交集”则是两个有序区间列表求交集,本质上是不断用两个区间取相交部分。还有会议室相关题目,判断同一时刻是否有多场会议冲突,同样是在检测区间是否重叠。
| LeetCode 题目 | 核心考点 | 和 223 题的关联 |
|---|---|---|
| 56. 合并区间 | 区间排序加合并 | 处理区间重叠的并集思路 |
| 986. 区间列表的交集 | 双指针求交集 | 区间交集公式复用 |
| 252 / 253. 会议室 | 判断时间冲突 | 重叠判断在具体场景下的应用 |
| 435. 无重叠区间 | 贪心选择 | 通过重叠判断做剔除 |
| 850. 矩形面积 II | 扫描线、线段树 | 两个矩形面积的 N 维扩展 |
你可以发现,223 题是整个区间相交主题里最简单的一个点。把它作为切入点,能平滑地走向热门 100 题里那些区间题。所谓刷题手感,很多时候不是靠背题,而是靠这样把相似模型串起来反复用。
5.3 这道题带来的复盘价值
我在实际刷题和面试准备中的体会是,像 223 题这种“代码量很小但覆盖知识点明确”的题,非常适合写进自己的知识点总结里。不要因为它简单就不复盘,反而应该把这类题当作一个“索引”,每次翻到这道题,就顺带复习一下一维区间相交公式、容斥原理、整数溢出检查这三个点。
理解到这一层之后,你再看 LeetCode 题解区、周赛复盘甚至热门 100 题里的相关讨论,会发现很多人聊的“套路”其实都来自这些最基础的小工具。比如我做周赛复盘时,只要看到某道题需要判断两个时间区间是否重叠,就会立刻回到 min(右端点) - max(左端点) 这个模型上,后面再根据题目要求选排序、贪心、堆还是线段树。
如果让我把这期总结压缩成一句话,那就是:矩形面积不是考点,矩形面积背后的降维思路才是。刷题的时候多问一句“这个二维问题能不能拆成两条一维问题”,很多看似复杂的几何题都会瞬间变得有章可循。
