LeetCode 391 完美矩形
在做矩形覆盖的题目时,很多人第一反应就是「遍历 + 合并区间」,但 LeetCode 的 完美矩形 题目可没那么简单。它不仅要求所有小矩形刚好拼出一个大矩形,而且不能有重叠、不能有缝隙,就像拼图一样必须严丝合缝。本文会带你逐步拆解问题,分析为什么简单的遍历不够用,并给出一个基于 面积校验 + 顶点校验 的高效解法。
摘要
在做矩形覆盖的题目时,很多人第一反应就是「遍历 + 合并区间」,但 LeetCode 的 完美矩形 题目可没那么简单。它不仅要求所有小矩形刚好拼出一个大矩形,而且不能有重叠、不能有缝隙,就像拼图一样必须严丝合缝。本文会带你逐步拆解问题,分析为什么简单的遍历不够用,并给出一个基于 面积校验 + 顶点校验 的高效解法。
描述
题目给你一组小矩形,每个矩形由左下角 (xi, yi)
和右上角 (ai, bi)
表示,矩形都是和坐标轴平行的。现在你要判断这些矩形是否能 完美拼接成一个更大的矩形。
判断标准主要有两个:
- 拼接后的整体区域必须是一个完整的大矩形。
- 小矩形之间不能重叠,也不能留下空隙。
换句话说,我们不仅要保证拼出来的总面积等于大矩形的面积,还要保证小矩形的边界刚好吻合。
实际场景类比:
可以想象一下装修时铺瓷砖:如果你要用一堆小方砖铺满一个长方形的地板,那么这堆砖必须正好覆盖整个地板,不能多也不能少,更不能有两块砖重叠。
题解答案
核心思路是 面积校验 + 顶点唯一性:
- 面积校验:所有小矩形的面积和必须等于大矩形的面积。
- 顶点校验:大矩形的四个顶点必须只出现一次,其它所有中间拼接的点必须出现偶数次(因为它们会被相邻矩形共享)。
一旦两个条件同时满足,就说明所有矩形能完美拼成一个大矩形。
题解代码分析
下面是完整的 Swift 实现代码:
import Foundation
class PerfectRectangle {
func isRectangleCover(_ rectangles: [[Int]]) -> Bool {
var minX = Int.max, minY = Int.max
var maxX = Int.min, maxY = Int.min
var areaSum = 0
var points = Set<String>()
func addOrRemovePoint(_ x: Int, _ y: Int) {
let key = "\(x),\(y)"
if points.contains(key) {
points.remove(key)
} else {
points.insert(key)
}
}
for rect in rectangles {
let x1 = rect[0], y1 = rect[1]
let x2 = rect[2], y2 = rect[3]
// 更新外接矩形边界
minX = min(minX, x1)
minY = min(minY, y1)
maxX = max(maxX, x2)
maxY = max(maxY, y2)
// 累加面积
areaSum += (x2 - x1) * (y2 - y1)
// 处理矩形四个顶点
addOrRemovePoint(x1, y1)
addOrRemovePoint(x1, y2)
addOrRemovePoint(x2, y1)
addOrRemovePoint(x2, y2)
}
// 计算外接矩形的面积
let expectedArea = (maxX - minX) * (maxY - minY)
if areaSum != expectedArea {
return false
}
// 外接矩形的四个顶点
let expectedPoints = [
"\(minX),\(minY)",
"\(minX),\(maxY)",
"\(maxX),\(minY)",
"\(maxX),\(maxY)"
]
// 校验点集合是否只剩下四个顶点
if points.count != 4 {
return false
}
for p in expectedPoints {
if !points.contains(p) {
return false
}
}
return true
}
}
代码解析
-
边界计算:通过遍历所有矩形,记录整个覆盖区域的最小
(x, y)
和最大(x, y)
,得到大矩形的外接边界。 -
面积验证:小矩形的面积总和必须和外接矩形的面积一致。
-
顶点处理:
- 对每个小矩形的四个顶点执行「异或」式的操作:如果顶点第一次出现就加入集合,如果再次出现就删除。
- 最终应该只剩下外接矩形的四个顶点。
-
返回结果:如果面积对上,顶点集合正确,就返回
true
。
示例测试及结果
let solver = PerfectRectangle()
print(solver.isRectangleCover([[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]]))
// 输出: true
print(solver.isRectangleCover([[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]]))
// 输出: false
print(solver.isRectangleCover([[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]]))
// 输出: false
结果解释:
- 第一个示例,小矩形严丝合缝拼成大矩形,返回
true
。 - 第二个示例有间隙,返回
false
。 - 第三个示例有重叠,返回
false
。
时间复杂度
- 遍历所有矩形一次,每个矩形只处理 4 个顶点。
- 时间复杂度为 O(n),其中 n 是矩形的数量。
空间复杂度
- 使用一个
Set
存储顶点,最多存储4n
个点。 - 空间复杂度为 O(n)。
总结
这道题如果直接用模拟的方法去拼接,复杂度会非常高,尤其是 n
可以达到两万的时候,根本不可行。
真正的突破点在于 把二维覆盖问题转化成面积 + 顶点唯一性验证:
- 面积保证了整体没有缝隙;
- 顶点唯一性保证了没有重叠。
这种解法思路非常优雅,既避免了暴力模拟,又保证了正确性。日常开发中,如果你遇到「多个区域拼接是否能完美组成大区域」的需求,比如地图划分、UI 布局检查,其实也可以借鉴这种「全局面积 + 局部边界」的思路来解决。
更多推荐
所有评论(0)