前几天刷到 LeetCode 第 223 题“矩形面积”,第一反应是“送分题”,无非就是两个矩形的面积相加,减掉重叠部分。真上手之后才发现,这道题其实把“几何建模”和“边界情况处理”都揉在了一起,而且面过多次之后回头再看,它串起了一批常见知识点,包括容斥原理、区间重叠判断、甚至扫描线的雏形。这篇文章就以 223 题为主线,把从坐标输入到面积计算的完整思路拆开,顺带聊聊我在这道题上学到的、以及后来在笔试面试里真正用上的东西。
223 题适合刚开始刷二维几何题的朋友,也适合准备面试时想快速回忆“区间相交”和“边界处理”这些高频考点的开发者。题目本身不复杂,但它的延展性极强,理解透了,后面做 836、850、LintCode 上的矩形并集问题会轻松很多。
1. 题目到底在考什么:别被“矩形”两个字带偏了
1.1 从坐标输入到数学建模
LeetCode 223 题会给你四个点坐标,分别代表两个矩形的左下角和右上角:
- 矩形一:左下角 (A, B),右上角 (C, D)
- 矩形二:左下角 (E, F),右上角 (G, H)
返回值是这两个矩形覆盖的总面积,重叠部分只算一次。
很多人一上来就想着“把两个矩形画出来”,然后分出几种相对位置去算。比如“矩形一在矩形二左边”“矩形一的右上角在矩形二内部”“一个完全包含另一个”……如果靠这种分类讨论去解题,也能做对,但代码会写得又臭又长,而且非常容易漏 case。
我建议第一步先做数学建模。记住一句话:二维矩形重叠问题,本质是两个一维区间问题的叠加。把矩形投影到 x 轴和 y 轴上,你会得到两条线段。判断矩形是否相交,其实就是在判断 x 方向的线段和 y 方向的线段是否同时相交。这个视角一旦建立起来,223 题就不需要“画图分类”了,全是数学运算。
1.2 为什么要用总面积减重叠面积
这道题最自然的思路是“容斥原理”。两个集合的并集面积等于它们的面积之和减去交集面积:
覆盖面积 = S1 + S2 - S_intersect
这个公式看起来简单到近乎废话,但它是后面一堆题目的理论基础。比如多个矩形求并集面积时,就用到了推广的容斥,或者用扫描线去避免重复计算。如果你连两个矩形的容斥都没吃透,后面理解扫描线会很有障碍。
所以,223 题表面是“算面积”,实际考的其实是两件事:
- 你是否能想到用容斥原理把未知的重叠面积问题拆成已知的矩形面积问题。
- 你是否能用区间相交的方式精确算出重叠矩形的边界,而不是靠复杂分类。
我印象很深的是,有一次模拟面试我现场写了分类讨论的解法,代码写到后半段自己都觉得要失控。面试官提醒了一句“你试试用 min 和 max 求相交区域”,整个逻辑立刻清晰了。这也是本文最想分享给你的核心技巧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 重叠区域怎么求:这题最值得学的“几何区间技巧”
2.1 相交矩形的四边公式
假设两个矩形确实相交,那它们重叠出来的区域一定还是一个矩形,这个矩形也有左下角和右上角,只是需要从两个原矩形中取边界。
重叠矩形的四条边可以用下面这套规则得到:
- 左边界 = max(A, E)
- 右边界 = min(C, G)
- 下边界 = max(B, F)
- 上边界 = min(D, H)
也就是说,重叠矩形的左下角是 (max(A, E), max(B, F)),右上角是 (min(C, G), min(D, H))。
为什么是这个规则?你可以这样理解:左边界不能比其他两个矩形的左边界更靠左,它必须同时位于两个矩形的右侧。要同时满足“在矩形一的右边”和“在矩形二的右边”,就得取两个左边界的较大值,也就是 max(A, E)。右边界也同理,它不能被任何一个矩形的右边界超出,所以取两个右边界的较小值,也就是 min(C, G)。
用生活里的例子类比:你在两个窗帘之间找“能同时被两盏台灯照到的区域”,右侧能照到的终点一定取决于更早熄灯的那一盏。重叠区域的左右边界,就是这种“公共部分最保守的边界”。
2.2 判断是否真的有重叠:宽高是否大于 0
上面计算边界只是第一步,关键问题在于:这两个矩形可能根本没有实际重叠。如果它们相离或者只共享一条边,那重叠矩形是不存在的,重叠面积也就是 0。
判断方法很直接:
code复制width = min(C, G) - max(A, E)
height = min(D, H) - max(B, F)
如果 width 和 height 都大于 0,说明重叠矩形真实存在,面积就是 width * height。如果 width 或 height 小于等于 0,说明没有面积意义上的重叠,直接取 0。
这里的小细节是“等于 0”的情况。两个矩形擦边重叠,也就是重叠区域变成了一条线或一个点,按照题目对“面积”的定义,这种情况重叠面积是 0,不需要扣减。处理时统一用“小于等于 0 即视为无重叠”的逻辑,能省掉很多边界判断,代码也更简洁。
2.3 完整参考代码:从公式到整数溢出防范
我把核心逻辑整理成下面这段 C++ 代码。虽然我用 C++ 写,但你完全可以照搬到 Python、Java、Go 中,代码结构是通用的:
cpp复制long long computeArea(int A, int B, int C, int D,
int E, int F, int G, int H) {
long long area1 = 1LL * (C - A) * (D - B);
long long area2 = 1LL * (G - E) * (H - F);
long long left = max(A, E);
long long right = min(C, G);
long long bottom = max(B, F);
long long top = min(D, H);
long long overlapWidth = right - left;
long long overlapHeight = top - bottom;
long long overlap = 0;
if (overlapWidth > 0 && overlapHeight > 0) {
overlap = overlapWidth * overlapHeight;
}
return area1 + area2 - overlap;
}
细心的朋友会注意到,我把中间结果都声明成了 long long 而不是 int。这是刻意为之。223 题原题里坐标范围是 -10^4 到 10^4,单看最坏情况,单个矩形面积约为 (2 * 10^4)² = 4 * 10^8,两个矩形总面积大约 8 * 10^8,并没有超过 int 的边界,直接返回 int 其实不会溢出。但计算过程中的 (C - A)、(D - B) 如果用 int 算,理论上是安全的,可一旦你把坐标范围放大到 10^9,或者像某些变种题那样矩形个数变多,int 就会爆。从一开始就养成“涉及面积用 64 位整数”的习惯,能省去之后一长串排查溢出的时间。
写到这里也顺便提一点代码风格的体会:我见过不少题解会把重叠区域的变量命名为 w 和 h,甚至直接在一行里算出重叠面积。这没什么不对,但为了面试现场讲述更顺畅,我建议把 left、right、bottom、top 写成有语义的名字,方便自己和面试官理清推导思路。代码终归是给人看的,可读性也是能力的一部分。
3. 题目延伸:两矩阵相交是开始,扫描线才是远方
3.1 变式一:多个矩形并集面积与扫描线
把 223 题的条件从“两个矩形”扩展到“N 个矩形”,让你求这些矩形覆盖的总面积,你会怎么做?如果继续用容斥原理,理论上可以,但事情会迅速变得无法收场:三个矩形时两两相交相减、再加三者交集,N 个矩形时集合组合数量爆炸,根本没法写。
这个场景下的标准解法是扫描线加线段树。把矩形的上下边变成事件线,从左到右扫,用线段树维护当前被覆盖的 y 轴区间长度,再乘以 x 轴的扫描步长,累加就是总面积。理解扫描线的前提,恰恰就是你得先懂得如何判断两个矩形是否相交,以及相交区域如何求边界。可以说 223 题是扫描线的最小前置知识。
LeetCode 850 题“矩形面积 II”就是典型的扫描线题目。它把扫描线、事件排序和线段树的懒更新结合起来,难度比 223 高了很多,但核心概念并没有脱离“区间覆盖长度”这个基础。我自己就是在刷完 223 之后过了一周去啃 850 的,明显感觉有坐标系建模基础之后,理解事件线和高度维护点容易得多。
3.2 变式二:矩形是否重叠的判断(LeetCode 836)
LeetCode 836 题问的是一个更基础的问题:两个矩形是否重叠,不要求算面积。这道题几乎就是 223 的子问题。你可以直接用宽度高度的公式:
code复制width = min(C, G) - max(A, E)
height = min(D, H) - max(B, F)
return width > 0 && height > 0;
注意这里要求的是“重叠”,所以严格大于 0,不包含边重合的情况。但 LeetCode 836 原题里明确表示边或角相接不算重叠,所以用 > 0 是对的。如果你做了 836,再回头做 223,会发现思路完全打通,因为 223 只是在判断重叠之后多算了一步面积。
我把 223、836、850 放在一起做了一个小对照表,方便你理解出题角度和难度跳跃:
| 题目 | 核心问题 | 重叠面积计算 | 推荐做法 | 难度 |
|---|---|---|---|---|
| 223 矩形面积 | 两个矩形覆盖总面积 | 需要 | 容斥原理 + 区间相交 | 中等(其实偏易) |
| 836 矩形重叠 | 两个矩形是否重叠 | 不需要 | 区间相交判断 | 简单 |
| 850 矩形面积 II | N 个矩形覆盖总面积 | 需要 | 扫描线 + 线段树 | 困难 |
从表格可以看出,223 在难度上处于一个很舒服的中间地带,往前可以接 836 的简单判断,往后可以接 850 的扫描线。我个人建议刷题时不要把 223 当孤题,而应该顺着这三道题组成一条线去学,比分散刷更容易建立“几何矩形问题”的知识树。
3.3 现实世界的应用:碰撞检测里的 AABB
如果觉得算法题离工程太远,我告诉你两个矩形相交这套模型在游戏开发和图形引擎里真的很常见。很多 2D 游戏里的物体碰撞检测会先把每个物体包在一个 AABB(Axis-Aligned Bounding Box,轴对齐包围盒)里,然后判断两个 AABB 是否相交。相交判断用的就是区间重叠的 min/max 公式,和 223 题一模一样。
我当年第一次在 Unity 项目里处理简单的 2D 拖拽碰撞时,就直接写过类似逻辑:拿到两个 UI 元素的 Rect,判断它们是否有交集,决定是否触发吸附。当时还没有系统刷过 LeetCode,写出来的代码是用矩形数量分类硬判的。刷完 223 之后再看那段代码,我发现完全可以用 min/max 简化成几行。算法题和工程实践之间不存在鸿沟,区别只在于是否能在需要时把模型抽象出来。
这也是我为什么强调“从几何到区间”的思维转换。矩形相交的判断之所以能用区间投影来表达,是因为 AABB 在世界坐标下跟 x/y 轴是对齐的,所以两轴方向的变化互相独立。一旦物体可以旋转,AABB 变成 OBB,这套公式就不成立了。面试时如果你能主动跟面试官提到类似的应用场景和局限性,会显得比单纯背解法的人成熟不少。
4. 面试与笔试中的避坑经验:从整数溢出到代码复盘
4.1 边界输入与溢出:这类题最阴的一点
LeetCode 223 的坐标范围是 [-10^4, 10^4],所以用 int 也不会真的溢出。但真正的笔试或面试官经常会把问题变形,问你“如果坐标范围是 [-10^9, 10^9] 怎么办”,或者让你手写“两个巨型矩形的覆盖面积”。此时 int 运算就可能直接溢出,甚至 width = right - left 这一步就会出问题。
我有个习惯,看到面积、距离、数量可能相乘的题目,第一反应就是考虑用 long long。写 C++ 时尤其要小心隐式类型转换:(C - A) * (D - B) 如果两侧都是 int,乘出来的结果也会先以 int 计算再赋值给 long long,这时候已经溢出了,再去转就晚了。正确做法是在相乘之前让至少一个操作数为 long long 类型,比如 1LL * (C - A) * (D - B),或者直接把 C、A、D、B 的差值拆到 long long 变量里再做乘法。Python 不存在这种问题,整数自动扩容,但同样建议保持数据类型的清晰,避免在 Java/C++ 的思维里写 Python 时不小心弄混。
下面这个例子是我在本地测试过的极端输入,long long 版本能正确输出,int 版本在某些语言里就挂了:
text复制矩形一:[-1000000000, -1000000000, 1000000000, 1000000000]
矩形二:[-1000000000, -1000000000, 1000000000, 1000000000]
两个矩形完全重叠,总面积应该就是 4 * 10^18。这个量级远超 int,所以笔试中如果题目没写范围,最安全的做法就是默认用 64 位去算面积。面试官在追问时也不是真的想知道你能不能算对,而是想看你会不会主动规避溢出风险。这是一项非常基础的工程素养。
4.2 边界情况梳理:哪些用例一定要写在测试列表里
很多人在 LeetCode 上提交代码,跑过题目给的示例就完事了,结果面试时被面试官手写测试用例问住。对于 223 题,我建议你至少把下面六类边界用例跑一遍:
- 两个矩形完全分离,例如一个在 x 轴方向的右侧,一个在左侧
- 两个矩形只在 y 轴方向有重叠,x 轴方向分离
- 两个矩形有一条公共边,重叠面积为 0
- 两个矩形只相交于一个点,重叠面积为 0
- 一个矩形完全包含另一个矩形
- 两个矩形完全重合
你会惊讶地发现,前四种情况用 width > 0 && height > 0 判断时,都能统一落到 overlap = 0 的分支,不用额外写任何特殊逻辑。这就是我前面强调“用数学区间而非视觉分类”的好处:代码天然覆盖了各种边界,而不是靠你逐个枚举再去写 if 判断。
我自己的习惯是在本地测试文件里把这六组用例写成单元测试,每次重构完代码都跑一遍。这样做看起来比直接提交多花几分钟,但对形成边界敏感度非常有帮助。刷题不完全是为了比赛,也是为了训练思维习惯,这种习惯会自然地延续到后续工程代码的测试设计中。
4.3 面试中怎么讲这道题:比正确答案更重要的推导过程
223 题在面试中出现的概率不算特别高,但它很适合作为“热身题”或“考察思维严密性”的题目。如果你在面试中遇到,建议不要直接上手写公式,而是先跟面试官说明思路:“两个矩形面积和是容易求的,关键只在于重叠区域是否为空以及它的面积如何求。重叠区域可以看作 x 轴和 y 轴两个区间分别求交集后的笛卡尔积,所以可以用 min 和 max 确定交集矩形的边界。”
这样讲的好处是让面试官知道你不是背了题解,而是真的有建模意识。接下来写代码时,你应该把变量名和注释都打得漂亮一点。写完代码不要急着喊“做完了”,而是主动补充你可能考虑的边界,比如“如果两个矩形只是边相接,宽度或高度其中之一为 0,此时重叠面积为 0,我这里的 if 判断已经覆盖了”。
有一次模拟面试,我讲完这段之后面试官追问了一句:“如果改成求两个矩形的最小包围矩形呢?”这个问题本质上是把 min/max 的用法倒过来:左下角取两个矩形左下角的较小值,右上角取两个右上角的较大值。因为思路已经通了,我大概用十秒就给出了回答。这也侧面说明,建立模型的收益会辐射到后续的变体追问中。
5. 从“刷题看到过”到“真正会用的”:我是怎么归纳这一类问题的
5.1 不要满足于 AC:做题之后的三个复盘步骤
很多人做完 223 题觉得简单,战绩 +1,就急着开下一道题。但我更推荐在做完题后做三个复盘步骤,这是我刷了几年题之后觉得性价比最高的学习方式:
第一步,重新把题目转化为自己的语言描述,越抽象越好。例如把 223 题抽象成“两个轴对齐矩形并集面积”,这能帮助自己和更复杂的扫描线题建立联系。
第二步,把当前题目放进更大的知识点网络里。223 属于“区间问题”,它又和数组上的区间合并、差分数组等知识有潜在关联。如果你想更系统化,可以顺手把 LeetCode 56 题“合并区间”拿来对比,它们共享的底层思想都是“区间与区间的关系只有分离、相交、包含三种形态”。
第三步,给自己出一道变式题。比如把矩形改成“给定 N 个矩形,求这些矩形覆盖的总周长”或“求这些矩形至少被两个矩形覆盖的面积”。如果自己能想出暴力解法,再去想优化,就会知道知识盲区在哪里。这种开放式练习比连续做十道相似难度的题更容易产生突破感。
5.2 那些热度很高但干扰你节奏的“热门题清单”
最近我注意到 LeetCode 相关热搜词里出现了“热门 100 题”“周赛 430”这类词汇。很多人喜欢从热门题单刷起,这本身没问题,但我想提醒一句:所谓热门 100 题一般是按频率和性价比选出来的,不一定适合每个人当前的知识结构。如果你动态规划还没入门,硬刷一堆“股票买卖”变种的题目只会积累挫败感。
我的建议是每周给自己定一个小目标,比如“本周解决 5 道几何题”或“本周扫完区间相交相关的题目”,而不是盲目追求刷题数量。用 223 题打头,再刷 836、850,这个组合就比随机从热门题单抓十道更紧凑,也更利于形成结构化的记忆。
5.3 以 223 题为例的“同类题解法速查思路”
最后分享一套适用于“矩形/区间重叠”类题目的快速思考顺序:
- 判断我处理的对象是不是同一种“区间”在不同维度上的笛卡尔积。
- 如果是,把维度拆开,单独看每一维上的区间是否重叠。
- 如果要求相交部分的具体值,就用 max 和 min 分别找下界和上界。
- 如果要求并集大小,先求总和再考虑容斥或更高级的扫描线。
- 运算中可能出现较大整数的地方,一律预留 64 位空间。
这套思考模型同样能套到不少不相干的题目上。比如二维矩阵上判断两个方块区域是否有重叠,或判断两个时间段是否有交集,本质上都是同一套方法。很多时候刷题刷得多了会发现,算法题不会真的穷尽,但底层模型的组合是有限的。能抓住模型,就能以不变应万变。
我个人在实际操作中的体会是,像 223 这样的题目,花十五分钟做完容易,但想做到“能迁移、能变通、能在面试现场讲清推导”,至少得再花一个小时去做延伸和复盘。这也是我写每一篇题解的习惯,先用最小时间 AC,再用最大比例时间思考它和已知题目的异同。如果你也想把刷题效果最大化,不妨从这道“简单但不简单”的矩形面积题开始,把容斥、区间相交、溢出处理这几个知识点一次性拿下。
