题目描述
我们有 N 个与坐标轴对齐的矩形, 其中 N > 0, 判断它们是否能精确地覆盖一个矩形区域。 每个矩形用左下角的点和右上角的点的坐标来表示。例如, 一个单位正方形可以表示为 [1,1,2,2]。 ( 左下角的点的坐标为 (1, 1) 以及右上角的点的坐标为 (2, 2) )。
示例:
rectangles = [
[1,1,3,3],
[3,1,4,2],
[1,3,2,4],
[2,2,4,4]
]
返回 false。因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。
解法1:扫描线+差分树状数组
分析:
- 只考虑矩形的竖线,扫描线从左到右扫描。
- 每扫描一次,就使用区间查询和区间修改来判断是否有重叠或空缺。
- 将矩形的所有竖线按照x坐标分组
- 竖线可以分为两类:入边和出边。在排序时注意将出边排列在入边之前
- 将y轴出现的坐标离散化
竖线的定义:
// 矩形的一条竖边
struct Line {
int x, y1, y2;
bool left; // 左边还是右边
Line() {}
Line(int x, int y1, int y2, bool left) : x(x), y1(y1), y2(y2), left(left) {}
bool operator<(const Line& b) {
if (x == b.x) { // 在x相同时,出边优先
return left < b.left;
}
else {
return x < b.x;
}
}
};
其余代码如下:
// 差分树状数组的定义
struct Diff {
...
};
bool isRectangleCover(vector<vector<int>>& rectangles) {
int rectNum = rectangles.size();
// 排序所有边,并统计y轴信息
vector<Line> lines(rectNum << 1);
map<int, int> yAxis;
for (int i = 0; i < rectNum; ++i) {
lines[i << 1] = Line(rectangles[i][0], rectangles[i][1], rectangles[i][3], true);
lines[(i << 1) + 1] = Line(rectangles[i][2], rectangles[i][1], rectangles[i][3], false);
yAxis.insert({ rectangles[i][1], 0 });
yAxis.insert({ rectangles[i][3], 0 });
}
sort(lines.begin(), lines.end());
// 将y轴坐标离散化
int yMax = yAxis.size() - 1;
auto yIter = yAxis.begin();
for (int i = 0; i <= yMax; ++i) {
yIter->second = i;
++yIter;
}
// 对边进行分组,同时确定y坐标对应的序号
vector<pair<int, int>> groups; // [l, r)下标分组
int preX = lines[0].x;
int preI = 0;
for (int i = 0; i < rectNum << 1; ++i) {
if (preX != lines[i].x) {
groups.push_back({ preI, i });
preX = lines[i].x;
preI = i;
}
lines[i].y1 = yAxis.find(lines[i].y1)->second;
lines[i].y2 = yAxis.find(lines[i].y2)->second;
}
groups.push_back({ preI, lines.size() });
// 对于按照x坐标划分的每一组竖线:进行区间加法,并判断是否满
Diff diff(yMax + 1);
for (int i = 0; i < groups.size() - 1; ++i) {
for (int j = groups[i].first; j < groups[i].second; ++j) {
if (lines[j].left) {
// 判断是否有重叠
if (diff.IntervalSum(lines[j].y1, lines[j].y2) != 0)
return false;
diff.UpdateInterval(lines[j].y1, lines[j].y2, 1);
}
else {
diff.UpdateInterval(lines[j].y1, lines[j].y2, -1);
}
}
if (diff.IntervalSum(0, yMax) != yMax)
return false;
}
return true;
}
解法2:点重合+面积法
分析:
- 完美矩形中的任何顶点,都出现了偶数次;处理最边缘的四个点
- 光靠点无法排除一些重叠的情况,使用面积判断即可。
代码如下:
bool isRectangleCover(vector<vector<int>>& rectangles) {
set<pair<int, int>> points;
int area = 0;
for (const auto& rect : rectangles) {
// 面积累计
area += (rect[3] - rect[1]) * (rect[2] - rect[0]);
// [0, 1]
if (points.find({ rect[0], rect[1] }) == points.end())
points.insert({ rect[0], rect[1] });
else
points.erase({ rect[0], rect[1] });
// [0, 3]
if (points.find({ rect[0], rect[3] }) == points.end())
points.insert({ rect[0], rect[3] });
else
points.erase({ rect[0], rect[3] });
// [2, 1]
if (points.find({ rect[2], rect[1] }) == points.end())
points.insert({ rect[2], rect[1] });
else
points.erase({ rect[2], rect[1] });
// [2, 3]
if (points.find({ rect[2], rect[3] }) == points.end())
points.insert({ rect[2], rect[3] });
else
points.erase({ rect[2], rect[3] });
}
if (points.size() != 4)
return false;
// 确定边际
int minX = INT32_MAX;
int minY = INT32_MAX;
int maxX = INT32_MIN;
int maxY = INT32_MIN;
for (const auto& point : points) {
minX = min(minX, point.first);
minY = min(minY, point.second);
maxX = max(maxX, point.first);
maxY = max(maxY, point.second);
}
// 核准面积
if (area != (maxY - minY) * (maxX - minX))
return false;
else
return true;
}