扫描线——391. 完美矩形
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了扫描线——391. 完美矩形相关的知识,希望对你有一定的参考价值。
题目
题目解析
扫描步骤:
- 把点扫描转为线扫描。把每个矩形用左右两边进行表示,如何表示边?(a,b,c) a代表这条边的x座标,而b,c代表对应的y座标。
- 把一竖行的所有线合并。但注意可能被隔开,但没关系,后续判断即可。
- 根据如图所示的规律进行求解。最左边的和最右边的一竖肯定只能是一竖线,而中间的每一竖线都有左右两竖线!
代码详解
java代码解析
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int n = rectangles.length << 1, index = 0;
//扫描线数组,四元组:[横坐标,纵坐标起点,纵坐标终点,左线段还是右线段]
int[][] lines = new int[n][4];
for (int[] rectangle : rectangles) {
//加入四元组,1表示左线段,-1表示右线段
lines[index++] = new int[]{rectangle[0], rectangle[1], rectangle[3], 1};
lines[index++] = new int[]{rectangle[2], rectangle[1], rectangle[3], -1};
}
//对扫描线进行排序预处理,方便后续的线段判断
Arrays.sort(lines, (o1, o2) -> o1[0] == o2[0] ? Integer.compare(o1[1], o2[1]) : Integer.compare(o1[0], o2[0]));
//分别存储相同横坐标下的 [左边线段] 和 [右边线段],在对横坐标进行一次处理后,存放的应该是对应方向合并后的线段
List<int[]> leftLine = new ArrayList<>(), rightLine = new ArrayList<>();
//双指针遍历每一个集合中的横坐标,通过横坐标获取到矩形的纵线段
for (int left = 0; left < n; ) {
int right = left;
leftLine.clear();
rightLine.clear();
//找到left横坐标相同的部分,使得区间[left, right)的横坐标都是相同的
while (right < n && lines[left][0] == lines[right][0]) right++;
//遍历这个区间内的线段(横坐标相同)
for (int i = left; i < right; i++) {
//得到当前横坐标上的线段,二元组 [纵坐标起始位置,纵坐标终止位置]
int[] yLine = new int[]{lines[i][1], lines[i][2]};
//引用传递,line直接指向左线段或者右线段集合
List<int[]> line = lines[i][3] == 1 ? leftLine : rightLine;
if (line.isEmpty()) line.add(yLine);
else {
//如果能进来这个else,说明在当前横坐标上有多条“左边”或“右边”,将当前边和上一次的边取出对比,如果当前边的纵坐标起点和上一条边出现了交叉,必然不是完美矩阵
int[] prevYLine = line.get(line.size() - 1);
//线段有交叉,说明必然有交叉矩阵,不符合题意
if (yLine[0] < prevYLine[1]) return false;
else if (yLine[0] == prevYLine[1]) {
//如开始的x坐标是1,对应的线段是左线段[1,3]和[3,4],当前线段和上一条线段能够刚好相接,直接修改上一条线段的纵坐标终止位置为当前线段的终止位置
prevYLine[1] = yLine[1];
} else {//@这个else暂时没看懂:现在懂了,如果没有被合并为一条线,则后面会进行判断的!
line.add(yLine);
}
}
}
if (left > 0 && right < n) {
//若不是完美矩形的边缘竖边,检查放入的左线段和右线段,因为在上面的循环操作中,合法线段都最后合并成一条,所以还需要比较左线段和右线段对应的起始和终止点
if (leftLine.size() != rightLine.size()) return false;
for (int i = 0; i < leftLine.size(); i++) {
if (leftLine.get(i)[0] == rightLine.get(i)[0] && leftLine.get(i)[1] == rightLine.get(i)[1]) continue;
return false;
}
} else {
//左边缘竖边在经过合并后存放大小为1的线段数组,如x=1时,存放的是[1,4],此时如果存在右边缘线段,必然不是完美矩形,反之亦然
if (leftLine.size() + rightLine.size() != 1) return false;
}
//移动left指针,继续寻找下一个x坐标对应的所有线段数组
left = right;
}
return true;
}
}
- 结合图来解释扫描线的这一段代码:
为什么不在这个位置直接return false?
因为如图所示,可能中间横插一块,导致无法合并为一条线,所以是直接就入队的形式。而后面对最左边和最右边的线进行特殊化处理,因为这两个线一定能合并为一条线。
对应代码
else {
line.add(yLine);
}
...
else {
//这是对最左和最右边的处理
if (leftLine.size() + rightLine.size() != 1) return false;
}
- 最关键的处理技巧:
只要这个合并后的竖线在内部,则必定是成双的(注意是一竖行一竖行的扫)!也就是两个格子由于都能被挨到,所以都会存在左右各合并成>=1条线的情况,而且线的数量上肯定是相等的!
cpp代码
class Solution {
public:
// cpp扫描线:实际上还是利用了完美矩形的特点,你可以从交点的情况分析,当然也可从合并边的方式分析。
//比如我这里就是从合并边的方向分析,具体而言就是:只要不是最左边和最右边的边,则肯定会有两条边一一对应。
//注意:是一竖行一竖行的验证
bool isRectangleCover(vector <vector<int>> &rectangles) {
int sz = rectangles.size();
rectangles.resize(2 * sz);
int idx = sz;
for (int i = 0; i < sz; i++) {//更新线
int x1 = rectangles[i][0], x2 = rectangles[i][2];
int y1 = rectangles[i][1], y2 = rectangles[i][3];
rectangles[i] = {x1, y1, y2, -1};
assert(idx < 2 * sz);
rectangles[idx++] = {x2, y1, y2, 1};
}
//排序线
sort(begin(rectangles), end(rectangles), [](vector<int> &a, vector<int> &b) {
if (a[0] != b[0])
return a[0] < b[0];
return a[1] < b[1];
});
//进行扫描线
int l = 0, r = 0;
vector <pair<int, int>> l1, l2;
while (r < 2 * sz) {
l1.clear();
l2.clear();
while (r < 2 * sz && rectangles[l][0] == rectangles[r][0])r++;//得出一竖行的所有点
for (int i = l; i < r; i++) {//进行一竖行线的合并
pair<int, int> cur = make_pair(rectangles[i][1], rectangles[i][2]);
vector <pair<int, int>> &line = rectangles[i][3] == -1 ? l1 : l2;
if (line.empty()) {
line.push_back(cur);
} else {
if (cur.first < line.back().second)//产生重叠
return false;
else if (cur.first == line.back().second)//合并
line.back().second = cur.second;
else {//可能中间存在分割
line.push_back(cur);
}
}
}
//合并完线以后进行三种情况的分析
if (l > 0 && r < sz * 2) {//处理中间线
if (l1.size() != l2.size())
return false;
else {
for (int i = 0; i < l1.size(); i++) {
if (l1[i].first != l2[i].first || l1[i].second != l2[i].second)
return false;
}
}
} else {//处理左右边缘线
if (l1.size() + l2.size() != 1)return false;
}
//继续往前推进
l = r;
}
return true;
}
};
以上是关于扫描线——391. 完美矩形的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 391 完美矩形[set 逻辑] HERODING的LeetCode之路
2022-01-21:完美矩形。 给你一个数组 rectangles ,其中 rectangles[i] = [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi,