目录
完美矩形
描述
给你一个数组 rectangles ,其中 rectangles[i] = [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi, yi) ,右上顶点是 (ai, bi) 。
如果所有矩形一起精确覆盖了某个矩形区域,则返回 true ;否则,返回 false 。
示例 1
输入:rectangles = [[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]] 输出:true 解释:5 个矩形一起可以精确地覆盖一个矩形区域。
示例 2
输入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]] 输出:false 解释:两个矩形之间有间隔,无法覆盖成一个矩形。
示例 3
输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[3,2,4,4]] 输出:false 解释:图形顶端留有空缺,无法覆盖成一个矩形。
示例 4
输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]] 输出:false 解释:因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。
提示
方法一:哈希表
精确覆盖有两个条件:
- 矩形区域不能有空缺,即矩形区域面积等于所有矩形的面积之和
- 矩形区域不能有相交区域
面积之和比较好计算,麻烦的是如何判断矩形区域是否重合了。
我们存储矩形的顶点坐标到哈希表中,记录该坐标的出现次数,保证大矩形周围的四个顶点出现次数为1次,其他坐标只能出现2次或者4次。
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int[] max = new int[4];//记录外围最大矩形的左下和右上坐标
max[0] = rectangles[0][0];//对左下坐标进行初始化
max[1] = rectangles[0][1];
max[2] = rectangles[0][2];//对右上坐标进行初始化
max[3] = rectangles[0][3];
HashMap<Point, Integer> map = new HashMap<>();//存储出现的坐标及出现的次数
int area=0;//记录所有矩形的面积
for (int i = 0; i < rectangles.length; i++) {
area+=(rectangles[i][2]-rectangles[i][0])*(rectangles[i][3]-rectangles[i][1]);//加上当前矩形的面积
if ((rectangles[i][0] + rectangles[i][1]) < max[0] + max[1]) {//如果新来的矩形超出了当前矩形的最大范围,更新边界
max[0] = rectangles[i][0];
max[1] = rectangles[i][1];
}
if ((rectangles[i][2] + rectangles[i][3]) > max[2] + max[3]) {//如果新来的矩形超出了当前矩形的最大范围,更新边界
max[2] = rectangles[i][2];
max[3] = rectangles[i][3];
}
Point point1 = new Point(rectangles[i][0], rectangles[i][1]);//左下的坐标
Point point2 = new Point(rectangles[i][0], rectangles[i][3]);//左上的坐标
Point point3 = new Point(rectangles[i][2], rectangles[i][1]);//右下的坐标
Point point4 = new Point(rectangles[i][2], rectangles[i][3]);//右上的坐标
map.put(point1,map.getOrDefault(point1,0)+1);//将当前坐标的出现次数+1
map.put(point2,map.getOrDefault(point2,0)+1);//将当前坐标的出现次数+1
map.put(point3,map.getOrDefault(point3,0)+1);//将当前坐标的出现次数+1
map.put(point4,map.getOrDefault(point4,0)+1);//将当前坐标的出现次数+1
}
//创建最大矩形的四个角坐标
Point point1 = new Point(max[0], max[1]);//左下的坐标
Point point2 = new Point(max[0], max[3]);//左上的坐标
Point point3 = new Point(max[2], max[1]);//右下的坐标
Point point4 = new Point(max[2], max[3]);//右上的坐标
if (area!=(max[2]-max[0])*(max[3]-max[1])||map.getOrDefault(point1,0)!=1||map.getOrDefault(point2,0)!=1||map.getOrDefault(point3,0)!=1||map.getOrDefault(point4,0)!=1){
return false;
}
map.remove(point1);
map.remove(point2);
map.remove(point3);
map.remove(point4);
for (Map.Entry<Point,Integer> entry : map.entrySet()) {
if (entry.getValue()!=2&&entry.getValue()!=4){
return false;
}
}
return true;
}
class Point {
public int x;
public int y;
public Point() {
}
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 temp = (Point) obj;
return this.x == temp.x && this.y == temp.y;
}
return false;
}
}
}
方法二:扫描线
将每个矩形 rectangles[i] 看做两条竖直方向的边,使用 (x,y1,y2) 的形式进行存储(其中 y1 代表该竖边的下端点,y2 代表竖边的上端点),同时为了区分是矩形的左边还是右边,再引入一个标识位,即以四元组 (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;
}
}