题目描述
给你一个数组 rectangles ,其中 rectangles[i] = [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi, yi) ,右上顶点是 (ai, bi) 。
如果所有矩形一起精确覆盖了某个矩形区域,则返回 true ;否则,返回 false 。
输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[3,2,4,4]]
输出:false
解释:图形顶端留有空缺,无法覆盖成一个矩形。
思路
需要满足3个条件
- 所有小矩形的和和大矩形面积相等
- 未重合的坐标只有4个
- 未重合的4个坐标是最外边的4个。
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int totalArea = 0;
int a = 0x3f3f3f3f, b = 0x3f3f3f3f;
int c = -0x3f3f3f3f, d = -0x3f3f3f3f;
Set coord = new HashSet();
for (int i = 0; i < rectangles.length; i++) {
int[] rectangle = rectangles[i];
totalArea += computeArea(rectangle[0], rectangle[1], rectangle[2], rectangle[3]);
a = Math.min(a, rectangle[0]);
b = Math.min(b, rectangle[1]);
c = Math.max(c, rectangle[2]);
d = Math.max(d, rectangle[3]);
String m = Integer.toString(rectangle[0]) + Integer.toString(rectangle[1]);
String n = Integer.toString(rectangle[0]) + Integer.toString(rectangle[3]);
String p = Integer.toString(rectangle[2]) + Integer.toString(rectangle[3]);
String q = Integer.toString(rectangle[2]) + Integer.toString(rectangle[1]);
if (coord.contains(m)) {
coord.remove(m);
} else {
coord.add(m);
}
if (coord.contains(n)) {
coord.remove(n);
} else {
coord.add(n);
}
if (coord.contains(p)) {
coord.remove(p);
} else {
coord.add(p);
}
if (coord.contains(q)) {
coord.remove(q);
} else {
coord.add(q);
}
}
String m = Integer.toString(a) + Integer.toString(b);
String n = Integer.toString(c) + Integer.toString(b);
String p = Integer.toString(c) + Integer.toString(d);
String q = Integer.toString(c) + Integer.toString(d);
if (coord.contains(m) && coord.contains(n) && coord.contains(p) && coord.contains(q)) {
return (totalArea == computeArea(a, b, c, d)) && (coord.size() == 4);
}
return false;
}
public int computeArea(int x1, int y1, int x2, int y2) {
return (x2 - x1) * (y2 - y1);
}
}
代码写的有点长,可能比较冗余,但是思路还是清晰的。
在遍历数组 rectangles 的过程中,不断记录 4 个 边界,作为最后大矩形;同时将各个小矩形的4个边界坐标放入 set 中,如果有一样的,就去除,这样可以保证最后集合中的是大矩形边界的点。
最后判断上面所说的三个条件就可以了。
题解
扫描线
将每个矩形 rectangles[i]rectangles[i] 看做两条竖直方向的边,使用 (x, y1, y2)(x,y1,y2) 的形式进行存储(其中 y1y1 代表该竖边的下端点,y2y2 代表竖边的上端点),同时为了区分是矩形的左边还是右边,再引入一个标识位,即以四元组 (x, y1, y2, flag)(x,y1,y2,flag) 的形式进行存储。
一个完美矩形的充要条件为:对于完美矩形的每一条非边缘的竖边,都「成对」出现(存在两条完全相同的左边和右边重叠在一起);对于完美矩形的两条边缘竖边,均独立为一条连续的(不重叠)的竖边。
如图(红色框的为「完美矩形的边缘竖边」,绿框的为「完美矩形的非边缘竖边」):
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int n = rectangles.length;
int[][] rs = new int[n * 2][4];
for (int i = 0, idx = 0; i < n; i++) {
int[] re = rectangles[i];
rs[idx++] = new int[]{re[0], re[1], re[3], 1};
rs[idx++] = new int[]{re[2], re[1], re[3], -1};
}
Arrays.sort(rs, (a,b)->{
if (a[0] != b[0]) return a[0] - b[0];
return a[1] - b[1];
});
n *= 2;
// 分别存储相同的横坐标下「左边的线段」和「右边的线段」 (y1, y2)
List<int[]> l1 = new ArrayList<>(), l2 = new ArrayList<>();
for (int l = 0; l < n; ) {
int r = l;
l1.clear(); l2.clear();
// 找到横坐标相同部分
while (r < n && rs[r][0] == rs[l][0]) r++;
for (int i = l; i < r; i++) {
int[] cur = new int[]{rs[i][1], rs[i][2]};
List<int[]> list = rs[i][3] == 1 ? l1 : l2;
if (list.isEmpty()) {
list.add(cur);
} else {
int[] prev = list.get(list.size() - 1);
if (cur[0] < prev[1]) return false; // 存在重叠
else if (cur[0] == prev[1]) prev[1] = cur[1]; // 首尾相连
else list.add(cur);
}
}
if (l > 0 && r < n) {
// 若不是完美矩形的边缘竖边,检查是否成对出现
if (l1.size() != l2.size()) return false;
for (int i = 0; i < l1.size(); i++) {
if (l1.get(i)[0] == l2.get(i)[0] && l1.get(i)[1] == l2.get(i)[1]) continue;
return false;
}
} else {
// 若是完美矩形的边缘竖边,检查是否形成完整一段
if (l1.size() + l2.size() != 1) return false;
}
l = r;
}
return true;
}
}
哈希表
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
long area = 0;
int minX = rectangles[0][0], minY = rectangles[0][1], maxX = rectangles[0][2], maxY = rectangles[0][3];
Map<Point, Integer> cnt = new HashMap<Point, Integer>();
for (int[] rect : rectangles) {
int x = rect[0], y = rect[1], a = rect[2], b = rect[3];
area += (long) (a - x) * (b - y);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, a);
maxY = Math.max(maxY, b);
Point point1 = new Point(x, y);
Point point2 = new Point(x, b);
Point point3 = new Point(a, y);
Point point4 = new Point(a, b);
cnt.put(point1, cnt.getOrDefault(point1, 0) + 1);
cnt.put(point2, cnt.getOrDefault(point2, 0) + 1);
cnt.put(point3, cnt.getOrDefault(point3, 0) + 1);
cnt.put(point4, cnt.getOrDefault(point4, 0) + 1);
}
Point pointMinMin = new Point(minX, minY);
Point pointMinMax = new Point(minX, maxY);
Point pointMaxMin = new Point(maxX, minY);
Point pointMaxMax = new Point(maxX, maxY);
if (area != (long) (maxX - minX) * (maxY - minY) || cnt.getOrDefault(pointMinMin, 0) != 1 || cnt.getOrDefault(pointMinMax, 0) != 1 || cnt.getOrDefault(pointMaxMin, 0) != 1 || cnt.getOrDefault(pointMaxMax, 0) != 1) {
return false;
}
cnt.remove(pointMinMin);
cnt.remove(pointMinMax);
cnt.remove(pointMaxMin);
cnt.remove(pointMaxMax);
for (Map.Entry<Point, Integer> entry : cnt.entrySet()) {
int value = entry.getValue();
if (value != 2 && value != 4) {
return false;
}
}
return true;
}
}
class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
return x + y;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Point) {
Point point2 = (Point) obj;
return this.x == point2.x && this.y == point2.y;
}
return false;
}
}
参考资料
https://leetcode-cn.com/problems/perfect-rectangle/solution/c-zui-tong-su-yi-dong-si-lu-by-heroding-npgj/
https://leetcode-cn.com/problems/perfect-rectangle/solution/gong-shui-san-xie-chang-gui-sao-miao-xia-p4q4/