题解
启发式解法-矩形分解
思路
因为多个矩形重叠部分的面积只需要计算一次,因此可以想到将重叠的多个矩形转化为多个不重叠的矩形,最后通过计算不重叠矩形中所有的矩形面积得到答案。
所需要的前置知识,针对任意两个矩形rec1, rec2
:
- 判断两个矩形是否重叠。两个矩形不重叠的条件为:
rec1.x1 >= rec2.x2 || rec2.x1 >= rec1.x2 || rec1.y1 >= rec2.y2 || rec2.y1 >= rec1.y2
,当满足其中至少一个条件时,说明一个矩形在另一个矩形的四周,不重叠。 - 当两个矩形重叠时,重叠部分也是一个矩形。设该矩形的坐标为:
(ox1, oy1, ox2, oy2)
,有:
ox1 = max(rec1.x1, rec2.x1)
ox2 = min(rec1.x2, rec2.x2)
oy1 = max(rec1,y1, rec2,y1)
oy2 = min(rec1.y2, rec2.y2)
笔者最开始的想法为:
遍历给定的矩形数组,与之前所有的矩形判断一次是否重叠,若重叠,则减去重叠部分的面积。但这里会存在问题,因为减去重叠部分可能是之前矩形公共的部分,只需要减一次,但这种方法可能会减去多次,所以是错误的。
后参考了该题解受到了启发:python 不知道该叫什么方法。。
思路如下:
- 记录一个已知不重叠的矩形列表:
unOverlapRectangleList
,其中的元素为每个矩形的坐标,可以用对象来存也可以用数组来存,这个随意。该列表的特点就在于其中的矩形都是没有重叠的。 - 依次遍历输入的每个矩形
rectangle[i]
,与unOverlapRectangleList
中的所有矩形进行判断:- 若均不重叠则可以直接将其加入列表
unOverlapRectangleList
; - 若与其中的某些矩形有重叠部分,则需要将
rectangle[i]
进行切分:- 将重叠的部分删除
- 将不重叠的部分切分为多个矩形,并将这些矩形继续与
unOverlapRectangleList
后续的元素进行判断、切分。 rectangle[i]
最终会被切分成多个不与unOverlapRectangleList
中矩形重叠的小矩形,将这些小矩形加入unOverlapRectangleList
中即可。
- 若均不重叠则可以直接将其加入列表
- 计算
unOverlapRectangleList
中每个矩形的面积,做累加求模,即可得到最后的答案。
那么又引出一个问题:如何将没有重叠的部分切分成多个小矩形呢?
因此:我们考虑矩形有重叠时可能出现的情况
矩形重叠情况
以下所有图中,重叠部分矩形用灰色阴影标出,
rectangle[i]
使用淡蓝色填充。两个矩形的坐标为(其实有3个,但是第三个矩形的坐标只用于计算重叠矩形的坐标,后续用不到):
rectangle[i]
的坐标为:(x1,y1,x2,y2)
- 重叠矩形
overlapRectangle
的坐标为:(ox1, oy1, ox2, oy2)
切分只竖直切,水平切也可以,但是不是水平+竖直,否则矩形过多,较为繁杂。
rectangle[i]
完全包围了当前判断中的矩形,可以切分出四个小矩形的情况:
通过已知的两个坐标我们可以得到切分出来的四个小矩形的坐标:
r1
的坐标为:(x1, y1, ox1, y2)
r2
的坐标为:(ox2, y1, x2, y2)
r3
的坐标为:(ox1, oy2, ox2, y2)
r4
的坐标为:(ox1, y1, ox2, oy1)
- 可以切分出三个小矩形的情况:
有以下四种,矩形编号仍与情况1中相同
可以发现,r1,r2,r3,r4
的坐标仍与情况1中的坐标相同
r1
的坐标为:(x1, y1, ox1, y2)
r2
的坐标为:(ox2, y1, x2, y2)
r3
的坐标为:(ox1, oy2, ox2, y2)
r4
的坐标为:(ox1, y1, ox2, oy1)
不同的地方在于,每一种情况中只有r1,r2,r3,r4
中的其中三个:
如左上图只有r1,r3,r4
,其中r2
坐标因为ox2 = x2
,导致矩形面积为0。因此我们只需要考虑面积不为0的矩形,剩下三种情况同理。
- 可以切分出两个小矩形的情况:
同样的,r1,r2,r3,r4
的坐标仍与上面的相同
r1
的坐标为:(x1, y1, ox1, y2)
r2
的坐标为:(ox2, y1, x2, y2)
r3
的坐标为:(ox1, oy2, ox2, y2)
r4
的坐标为:(ox1, y1, ox2, oy1)
而每一种情况中只有r1,r2,r3,r4
中的其中两个:
如左上图只有r1,r4
,其中r2
坐标因为ox2 = x2
,导致矩形面积为0;r3
的坐标因为oy2=y2
,导致面积为0。因此我们只需要考虑面积不为0的矩形,剩下三种情况同理。
- 可以切分出一个小矩形的情况:
同样的,r1,r2,r3,r4
的坐标仍与上面的相同
r1
的坐标为:(x1, y1, ox1, y2)
r2
的坐标为:(ox2, y1, x2, y2)
r3
的坐标为:(ox1, oy2, ox2, y2)
r4
的坐标为:(ox1, y1, ox2, oy1)
而每一种情况中只有r1,r2,r3,r4
中的其中一个:
如左上图只有r1
,其中r2
的坐标因为ox2 = x2
,导致矩形面积为0;r3
的坐标因为oy2 = y2
,导致面积为0;r4
的坐标因为y1 = oy1
,导致面积为0。因此我们只需要考虑面积不为0的矩形,剩下三种情况同理。
- 无法切分出小矩形的情况:
这种情况仍可以得到四个矩形r1,r2,r3,r4
,坐标与上述计算仍然相同,只是面积均为零,不需要做考虑。
代码实现
通过上述的思路,可以实现代码:
class Rectangle {
public int x1;
public int x2;
public int y1;
public int y2;
public Rectangle(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public long area(){
return Rectangle.area(this);
}
public static long area(Rectangle rectangle){
return Rectangle.area(new int[]{rectangle.x1, rectangle.y1, rectangle.x2, rectangle.y2});
}
public static long area(int[] rec){
return (long) (rec[2] - rec[0]) * (rec[3] - rec[1]);
}
}
public class Solution {
public static final int MOD = 1000000007;
private List<Rectangle> unOverlapRectangleList = new LinkedList<>();
public int rectangleArea(int[][] rectangles) {
long ans = 0;
int n = rectangles.length;
for (int i = 0; i < n; i++) {
processOverlapRectangle(new Rectangle(rectangles[i][0], rectangles[i][1],rectangles[i][2],rectangles[i][3]));
}
for (Rectangle rectangle : unOverlapRectangleList) {
ans += rectangle.area();
}
return (int) (ans % MOD);
}
private void processOverlapRectangle(Rectangle rectangle){
//将当前的矩形切分成与已知矩形不重叠的多个小矩形,直至遍历完所有的已知矩形
//最后将切分完的小矩形列表添加到已知矩形列表中
List<Rectangle> newRectangles = new LinkedList<>();
List<Rectangle> temp = new LinkedList<>();
newRectangles.add(rectangle);
for (Rectangle existRectangle : unOverlapRectangleList) {
for (Rectangle newRectangle : newRectangles) {
if (!isOverlap(newRectangle, existRectangle)){
//如果没有重叠部分,则添加到暂存的列表中,遍历下一个元素
temp.add(newRectangle);
continue;
}
//重叠了,则需要对当前新的矩形进行切分,将重叠部分删去,得到剩余部分的矩形分解
//切分的结果为一个列表,其中的矩形元素与已知的矩形不重叠且面积大于0。
List<Rectangle> segments = segment(newRectangle, existRectangle);
temp.addAll(segments);
}
//将暂存列表中的所有矩形拿出来作为下一次迭代时判断的新矩形列表
newRectangles.clear();
newRectangles.addAll(temp);
//清空暂存列表
temp.clear();
}
//将切分出来的矩形加入已知不重复的矩形列表中
unOverlapRectangleList.addAll(newRectangles);
}
public List<Rectangle> segment(Rectangle newRectangle, Rectangle existRectangle){
//首先得到重叠部分矩形坐标
Rectangle overlapRectangle = overlapRectangle(newRectangle, existRectangle);
//根据重叠部分的坐标获得四个切分后的矩形
LinkedList<Rectangle> segments = new LinkedList<>();
segments.add(new Rectangle(newRectangle.x1, newRectangle.y1, overlapRectangle.x1, newRectangle.y2));
segments.add(new Rectangle(overlapRectangle.x2, newRectangle.y1, newRectangle.x2, newRectangle.y2));
segments.add(new Rectangle(overlapRectangle.x1, overlapRectangle.y2, overlapRectangle.x2, newRectangle.y2));
segments.add(new Rectangle(overlapRectangle.x1, newRectangle.y1, overlapRectangle.x2, overlapRectangle.y1));
//过滤掉面积为0的矩形
return segments.stream().filter(rec-> rec.x1 != rec.x2 && rec.y1 != rec.y2).collect(Collectors.toList());
}
//判断两个矩形是否有重叠
public boolean isOverlap(Rectangle rec1, Rectangle rec2){
// xi1 >= xj2 || xj1 >= xi2 || yi1 >= yj2 || yj1 >= yi2
// 一个矩形在另外一个矩形四周
return !(rec1.x1 >= rec2.x2 || rec2.x1 >= rec1.x2 || rec1.y1 >= rec2.y2 || rec2.y1 >= rec1.y2);
}
// 得到重叠部分矩形的坐标
public Rectangle overlapRectangle(Rectangle rec1, Rectangle rec2){
int overLapAreaX1 = Math.max(rec1.x1, rec2.x1);
int overLapAreaX2 = Math.min(rec1.x2, rec2.x2);
int overLapAreaY1 = Math.max(rec1.y1, rec2.y1);
int overLapAreaY2 = Math.min(rec1.y2, rec2.y2);
return new Rectangle(overLapAreaX1,overLapAreaY1,overLapAreaX2,overLapAreaY2);
}
}