LeetCode 3047这道题,第一次在周赛里看到的时候,我第一反应是“又是几何模拟题”,但真正上手之后发现,它考的核心其实是一维区间合并的思维,根本不需要复杂的计算几何知识。题名很长——求交集区域内的最大正方形面积,翻译成人话就是:给你一堆矩形,任意选出两个,看它们的重叠区域能不能塞下一个正方形,能塞下的最大正方形面积是多少,没有重叠就是0。
这题适用面挺广的:正在刷LeetCode热题100的同学、准备周赛的选手、还有面试前想快速过一遍“几何类暴力枚举”套路的工程师,都能从这题里拿到一些可以直接迁移的思考方式。它不考高深算法,但特别考察“把二维问题降维成一维”的敏感度,以及边界条件的严谨性。我这次就把完整的推导过程、代码实现、还有我踩过的坑一起整理出来,希望对你有帮助。
1. 题目到底在问什么:从二维相交到一维区间的思维转换
1.1 先看懂坐标系和矩形的表示方式
题目给出的每个矩形是一个四元组,通常记作 [x1, y1, x2, y2],分别代表左下角坐标 (x1, y1) 和右上角坐标 (x2, y2)。为什么强调左下角和右上角?因为题目明确说了任意矩形都是轴对齐矩形(边平行于坐标轴),这样 x1 < x2、y1 < y2 恒成立。
这组坐标的语义很关键。有些同学做几何题习惯用“左上角+右下角”表示矩形,拿到这道题会下意识套用,结果算交集的时候底边和顶边搞反,白送几个WA。所以第一步先确认:题目给的 (x1, y1) 是左下角,(x2, y2) 是右上角,水平方向范围是 [x1, x2],垂直方向范围是 [y1, y2]。
一个矩形之所以是矩形,本质是两组合法区间的笛卡尔积:x 方向一个连续区间,y 方向一个连续区间。这么看之后,矩形面积其实就是“区间长度相乘”,矩形是否相交其实就是“两个方向上的区间是否都有交集”。这是整道题最重要的转念。
1.2 两个矩形相交,交集为什么还是矩形
如果两个矩形相交,它们的重叠区域是什么形状?想象两个方形的便利贴叠在一起,只要不是完全错开,重叠部分一定还是一个矩形。注意,这里是轴对齐矩形,所以不会出现斜边,重叠部分的边界一定由原来两个矩形的边拼成。
用集合的角度看更严谨。矩形A可以写成:
- 水平区间
[a_x1, a_x2] - 垂直区间
[a_y1, a_y2]
矩形B同理。两个矩形的交集就是同时满足A和B坐标约束的点集,所以水平方向必须同时落在两个水平区间内,垂直方向同理。于是交集区域就是:
- 水平区间
[max(a_x1, b_x1), min(a_x2, b_x2)] - 垂直区间
[max(a_y1, b_y1), min(a_y2, b_y2)]
只要新区间的左端小于右端、下端小于上端,交集就是一个合法的矩形。这个性质是所有“矩形交集类题目”的基础,记住这个形式,后面所有代码都是围绕它展开的。
1.3 怎么把“最大正方形”转化为区间运算
交集区域确定之后,“能放下的最大正方形”就很好算了。正方形四边相等,边平行于坐标轴放置时,它要放进一个 width * height 的矩形里,最大边长只能取 min(width, height)。这个结论很直观:如果边长大于短边,那条短边方向上就放不下了;等于短边时,正方形恰好内接于矩形的一边。
所以每个矩形对的候选正方形面积就是:
side = min(交集宽度, 交集高度)- 面积 =
side * side
题目要求所有矩形对里的最大值,也就是两两枚举,取最大面积。这一步把“几何图形”彻底转化成了“区间求交 + 取最小值”,复杂度落到了矩形对的数量上,而不是图形面积上。很多同学觉得这题难,其实是卡在“不知道交集矩形坐标怎么求”,一旦想通,核心代码不过十几行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法设计:暴力枚举所有矩形对
2.1 为什么O(n²)足够快,不需要优化
看到“枚举所有矩形对”,有些做过大型计算几何题的同学会本能地警惕:n 到 10^5 不就炸了吗?但LeetCode 3047这道题的 n 只有几百(实际原题约束在500左右,不同版本可能有细微差异),所以 O(n²) 枚举完全无压力。
我刷题时的习惯是:先看数据范围,再定算法,而不是一上来就追求最优解。这题就是在考“你是否能把几何模型转换成坐标公式”,而不是考“你会不会线段树或扫描线”。即使未来碰到的版本把 n 放大到 10^4,O(n²) 也依然能扛住几百万量级的计算;真到 10^5,那才需要扫描线之类的进阶方案,但那是另一个话题了。
两层循环的枚举顺序也注意一下:外层 i 从0到n-1,内层 j 从 i+1 到n-1,保证每个矩形对只算一次,同时天然避免“自己和自己求交集”的问题。虽然自己和自己肯定有交集,题目要求是“任选两个不同矩形”,所以必须 i < j。别小看这一点,我见过不少人写 for i, for j 忘了跳过同一个下标,结果答案莫名偏大。
2.2 判定交集的核心公式与几何直觉
两个矩形交集区域的四个边界值,其实不需要画图也能推出来。拿水平方向来说:
- 交集的下边界,必须是两个矩形下边界里更靠上的那个,所以是
max(x1_i, x1_j)。 - 交集的上边界,必须是两个矩形上边界里更靠下的那个,所以是
min(x2_i, x2_j)。
水平和垂直方向完全对称,所以交集矩形就是:
left = max(x1_i, x1_j)bottom = max(y1_i, y1_j)right = min(x2_i, x2_j)top = min(y2_i, y2_j)
然后判断 left < right && bottom < top,如果成立,说明交集确实存在;否则交集为空,面积为0。
这里有个很常见的笔误:有些同学会写 left >= right || bottom >= top 时 continue,这个写法对,但要注意临界情况。如果 left == right,交集是一条竖线,面积是0,放不下任何正面积正方形,所以用 >= 判断是必要的,不能用 > 。
2.3 代码实现:Java版本
我用Java写一版,尽量保持结构清晰,方便直接背诵模板。
java复制class Solution {
public long largestSquareArea(int[][] rectangles) {
int n = rectangles.length;
long ans = 0;
for (int i = 0; i < n; i++) {
int[] a = rectangles[i];
for (int j = i + 1; j < n; j++) {
int[] b = rectangles[j];
// 交集矩形的四条边界
long left = Math.max(a[0], b[0]);
long bottom = Math.max(a[1], b[1]);
long right = Math.min(a[2], b[2]);
long top = Math.min(a[3], b[3]);
if (left < right && bottom < top) {
long side = Math.min(right - left, top - bottom);
ans = Math.max(ans, side * side);
}
}
}
return ans;
}
}
注意我把 left、bottom、right、top 都声明成了 long,这是因为坐标本身可能是 10^9 量级,计算 side * side 时结果可能到 10^18,超过 int 范围。用 long 不仅是返回值需要,中间计算也必须避免溢出。后面我还会专门讲这个坑。
2.4 Python版本:更简洁但不省边界检查
Python选手写这题会更短,因为 max/min 用起来顺手,而且Python整数没有溢出问题。但越简洁越容易漏判,边界条件依然要写清楚。
python复制class Solution:
def largestSquareArea(self, rectangles: List[List[int]]) -> int:
n = len(rectangles)
ans = 0
for i in range(n):
x1a, y1a, x2a, y2a = rectangles[i]
for j in range(i + 1, n):
x1b, y1b, x2b, y2b = rectangles[j]
left = max(x1a, x1b)
bottom = max(y1a, y1b)
right = min(x2a, x2b)
top = min(y2a, y2b)
if left < right and bottom < top:
side = min(right - left, top - bottom)
ans = max(ans, side * side)
return ans
Python版有一个隐性好处:side * side 永远不用担心溢出,刷题时可以用直觉直接写。但刷C++或Java版本时,溢出就要格外小心,这也是为什么我一直建议主语言为Java/C++的读者至少把两个版本都写一遍:语言差异会逼你注意类型范围的细节。
3. 复杂度分析与边界条件避坑
3.1 时间复杂度与空间复杂度
这题的性能瓶颈就是两层循环:
- 外层从0到n-1
- 内层从i+1到n-1
总枚举次数是 n * (n - 1) / 2,也就是 O(n²)。每次循环内只做常数次 max/min 运算和一个乘法,所以总时间复杂度是 O(n²)。空间上只用了一个 ans 变量和几个临时变量,额外空间 O(1)。
对一个 n=500 的用例来说,这个操作数大概12万次左右,跑起来就是零点几毫秒的事情。就算 n 放大到2000,也就是200万次操作,依然轻松通关。所以看到这题,第一目标就是把枚举写好、把边界判对,而不是急着用高级数据结构。
3.2 最容易踩的三个坑:交集为空、自交、坐标语义
第一个坑是交集为空的判断。两个矩形如果正好边贴边,比如一个矩形的右边界等于另一个矩形的左边界,它们的“重叠”是一条线,没有面积,不能放正方形。所以判断条件必须是 left < right && bottom < top,用严格小于。如果用 <=,这条线也会被当成有面积,然后算出一个 side = 0,不会影响最终答案,但如果后续代码有 if (side > 0) 之类的额外逻辑,风格就会不够严谨。
第二个坑是自交。如果不限制 i < j,某些情况下程序会把“矩形自己和自己的交集”也考虑进去,那结果至少是这个矩形的内接最大正方形面积,很可能比任意两个不同矩形的交集还大,答案直接错。所以枚举时统一用 i < j,从源头避免。
第三个坑是坐标语义。题目坐标是 [x1, y1, x2, y2] 表示左下角和右上角,但有些LeetCode题目(比如另一类矩阵题)会用 [x1, y1, x2, y2] 表示左上角和右下角。两种表示中 x 的位置一致,但 y 方向的计算方式完全不同,不能想当然套公式。拿到题目第一件事应该是确认:哪个点是左下角?哪个点是右上角?这一步错了,后面所有推演都白搭。
3.3 用long避免溢出,这是中等题最常见的隐藏杀手
LeetCode的坐标范围在原题中可能给到 10^9,而矩形数量不大,所以很多人就放松了警惕。但实际上,坐标差最大能到 10^9,两个差相乘就是 10^18,而Java的 int 最大值只有约 2.1 * 10^9,直接溢出成负数。更阴间的是,即使最终答案不会超 int,中间 side * side 一旦溢出,结果就是一个随机负数,Math.max 取不到它也就罢了,万一溢出出正数反而可能污染答案。
所以我建议:所有坐标和答案相关的变量一律用 long,尤其是Java和C++。Python用户不需要担心,但如果你用C++,记得把 long long 安排上。这也是这道题被称为“中等”而不是“简单”的原因之一——隐蔽的数据范围陷阱,专门过滤粗心的选手。
4. 刷题场景下的变体与延伸
4.1 如果矩形数量放大到10^5,怎么继续做
虽然原题 n 很小,但如果你在面试时被追问“如果 n 很大怎么办”,不能只会 O(n²)。这时候可以往扫描线或者线段树方向想:把所有矩形看成平面上的事件,用一棵维护区间纵坐标的线段树,按横坐标扫描,边扫边查询当前横坐标区间内最大的纵坐标交集长度,再取 min 作为候选边长。具体实现比较复杂,一般面试不会要求写到bug-free,但至少能说出思路:先按横坐标排序,维护竖直方向活跃区间的合并长度,并实时更新全局最大 min(当前横跨度, 当前纵重叠长度)。
还有一招更 trick 的:如果题目只问矩形对的最大交集,且 n 到 10^5,通常会有额外限制,比如坐标范围小、或者所有矩形都是相同大小。这种题就落到“排序+优先队列”上了:按左下角横坐标排序,逐个加入,维护一个最小堆存右上角横坐标,保证当前处理的矩形只跟横坐标尚未分离的矩形比较。这样理论上能减少大量无效比较,但最坏复杂度仍是 O(n²),只能算工程优化,不算理论优化。
4.2 如果坐标改成浮点数,需要注意什么
原题坐标是整数,所以判断 left < right 没有任何毛病。但如果题目改成浮点坐标,这种严格比较就要换成 left + eps < right && bottom + eps < top,eps 一般取 1e-9 或 1e-10。为什么这样?因为浮点数运算有精度误差,两个理论上不相交的矩形,由于一边边界算出来恰好非常接近另一边,可能被误判成相交,然后得出一个极小极小的正方形面积。虽然最终答案大概率不受影响,但在精确度要求高的几何题里,这种误差处理是必须养成的习惯。
4.3 如果改成求最大重叠面积,代码怎么改
这题的变体也很多,最常见的是“求两个矩形的最大重叠面积”,那核心公式直接变成 area = (right - left) * (top - bottom),不用再取 min,也不需要保证是正方形。这个变体其实是 3047 的降级版,很多人会先做了重叠面积再做本题,会发现正方形约束才是题眼所在。理解了“最大正方形边长 = 交集矩形短边”这个逻辑,两个变体就打通了。
还有一种延伸是求“最小外接正方形面积”,也就是给定两个矩形,找到一个能同时覆盖它们的最小正方形。这个题目就从“求交集”变成“求并集”,需要分别考虑水平方向和垂直方向的跨度,再取 max,可以做题时对照着练习。
5. 这题对应的刷题心法与周赛实战心得
5.1 看到“矩形交集”,第一反应永远是坐标公式
我刷了这么多年题,每次看到“两个矩形交集”第一反应就是套四元组公式,而不是画图。画图只能帮你理解样例,真正处理边界和复杂用例时,必须靠坐标推导。这题的坐标公式本质上就是两个一维区间的交集,我们可以把它抽象成一个小工具函数:
java复制// 一维区间交集长度
private long intersectLength(long a1, long a2, long b1, long b2) {
long left = Math.max(a1, b1);
long right = Math.min(a2, b2);
return left < right ? right - left : 0;
}
有了这个函数,二维矩形的交集就可以拆成“水平区间交集长度”和“垂直区间交集长度”两个一维问题的组合。这种拆分能力在计算几何、图像处理甚至游戏碰撞检测里都是通用的,值得多写几遍形成肌肉记忆。
5.2 周赛实战里的时间分配:简单题拿来练手,中等题重在吃透
3047 出现在周赛的第2题或第3题位置,属于“中等题里的友好题”。它的考察点不是高级算法,而是“你够不够细心、能不能快速建模”。我在周赛里遇到这题时,大概花了不到5分钟写完,剩下的时间都留给了后面的困难题。为什么能这么顺?因为之前做过太多几何模拟题,已经形成了固定套路:先确认坐标表示,再写交集公式,然后套平方。
如果你正在备战周赛,建议把这类题总结成“送分题模板”。周赛最怕的不是不会做,而是把时间浪费在犹豫“要不要优化”上。对于 n = 500 的题,O(n²) 就是标准解,不需要任何高级优化。想通这一点,你的心态会稳很多。
5.3 从这题抽象出的“几何模拟题”通用解题顺序
最后分享一个我个人总结的几何模拟题解套路线,对3047以及类似题目都适用:
- 确定坐标表示方式,写清楚左下角、右上角对应的变量名。
- 写出交集矩形四边界的计算公式,用
max取下界,min取上界。 - 判断交集是否为空,注意严格小于。
- 根据题目要求计算目标值(面积/边长/是否重叠等)。
- 涉及乘法时,提前评估数值范围,选择合适的整型类型。
这套顺序我用了很多年,从USACO到LeetCode周赛,基本没翻过车。建议大家也把它变成自己的默认流程,遇到“两个矩形相交”相关题目,直接无脑走一遍,能省下大量试错时间。
说实话,3047这道题本身不难,但它背后“二维几何问题转一维区间问题”的思想,却在很多题里反复出现。比如求多个矩形的覆盖面积、判断两个矩形是否重叠、甚至计算两个区间数组的重叠长度,核心都是这套max/min框架。把这题彻底吃透,比你盲目刷10道同类型的新题更有价值。我每次复盘周赛都会把这类“方法简单但容易出错”的题挑出来,反复默写,实战的正确率就是这么一点一点堆出来的。
