Given N axis-aligned rectangles where N > 0, determine if they all together form an exact cover of a rectangular region.
Each rectangle is represented as a bottom-left point and a top-right point. For example, a unit square is represented as [1,1,2,2]. (coordinate of bottom-left point is (1, 1) and top-right point is (2, 2)).
Example 1:
rectangles = [
[1,1,3,3],
[3,1,4,2],
[3,2,4,4],
[1,3,2,4],
[2,3,3,4]
]
Return true. All 5 rectangles together form an exact cover of a rectangular region.
Example 2:
rectangles = [
[1,1,2,3],
[1,3,2,4],
[3,1,4,2],
[3,2,4,4]
]
Return false. Because there is a gap between the two rectangular regions.
具体题目见leetcode原题, 这题是leetcode周赛第二次的压轴题。首先自己得知道怎么判断是否是刚好覆盖矩形区域。也就是算法层面的判断准则。
1. 矩形轮廓的要求:有且仅有四个单一的点存在,也就是其他的点至少有两次;
2. 没有重叠的要求:每个点不能同时充当多个矩形的同一位置点,也就是不能同时是俩矩形的左下角或者左上角之类的。
3. 总要求:每次累加小矩形面积,最后计算大矩形面积,必须相等,这样就没有重叠没有间隔。
鉴于上面的规则,我们可以遍历一遍数组,然后我们需要统计,点的坐标系位置(x, y),点的矩形功能位置(左右上下),以及出现次数。这里可以有的基本做法是,建立一个:
unordered_map<int, unordered_map<int, int>> m;
m[x][y]++;
这样就可以统计每一组坐标出现的次数,最后检验出现次数为1的是否只有四个。
对于位置判断,可以用1,2,3,4表示四个角的位置,只要每一组坐标不要在同一个位置有重复就可以。
unordered_map<int, unordered_map<int, vector<int>>> m;
m[x][y].push_back(1.2.3.4?);
遍历的时候,将出现过的坐标添加进最后的vector, 同时判断加的新位置出现过没,一旦出现返回错误。
上述思想用数据结构实现的比较繁琐,这里就体现了大神做法的高明,在表示种类区分这种事上,二进制中1的位置一直是一个很好的做法,我们可以用1(0001),2(0010),4(0100),8(1000)表示四个角,然后如果对每个坐标伴随着新矩形位置进行检索的时候,做位与运算,如果结果不为0,那么证明位置有重叠,返回错误,如果没有重叠就做位或运算,添加一个新出现的位置。这样就解决了最为麻烦的条件2,条件1就直接数最后1,2,4,8的个数是不是4个就行。同时面积的问题只要累加每个小面积同时检索的时候记录最小最大的x,y坐标就行。代码如下:
bool isRectangleCover(vector<vector<int>>& rectangles) {
unordered_map<string, int> m;
int minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN;
int area = 0;
for (auto & rect: rectangles) {
// 统计四个Corner的值
minx = min(minx, rect[0]);
miny = min(miny, rect[1]);
maxx = max(maxx, rect[2]);
maxy = max(maxy, rect[3]);
// 逐一计算面积累加
area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
// 判断是否有overlap
if (!isValid(m, to_string(rect[0]) + "_" + to_string(rect[1]), 1)) return false; // bottom-left
if (!isValid(m, to_string(rect[0]) + "_" + to_string(rect[3]), 2)) return false; // top-left
if (!isValid(m, to_string(rect[2]) + "_" + to_string(rect[3]), 4)) return false; // top-right
if (!isValid(m, to_string(rect[2]) + "_" + to_string(rect[1]), 8)) return false; // bottom-right
}
int cnt = 0;
for (auto it = m.begin(); it != m.end(); ++it) {
int t = it->second;
if (t != 15 && t != 12 && t != 10 && t != 9 && t != 6 && t != 5 && t!= 3) {
++cnt;
}
}
if ((cnt == 4) && (maxx - minx) * (maxy - miny) == area) return true;
else return false;
}
bool isValid(unordered_map<string, int>& m, string corner, int type) {
int& val = m[corner];
if (val & type) return false;
val |= type;
return true;
}
后来发现,其实只要1,3两个条件就能满足所有情况,这个我并不知道怎么证明,但确实也没找到反例,leetcode也是通过没问题。这样一来代码也简化了很多。
bool isRectangleCover(vector<vector<int>>& rectangles) {
unordered_map<int,unordered_map<int, int>> m;
int minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN;
int area = 0;
for (auto& rect: rectangles) {
// 统计四个Corner的值
minx = min(minx, rect[0]);
miny = min(miny, rect[1]);
maxx = max(maxx, rect[2]);
maxy = max(maxy, rect[3]);
// 逐一计算面积累加
area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
// 判断是否有overlap
m[rect[0]][rect[1]]++;
m[rect[0]][rect[3]]++;
m[rect[2]][rect[1]]++;
m[rect[2]][rect[3]]++;
}
for (auto& itx: m) {
int x = itx.first;
for (auto& ity: itx.second) {
int y = ity.first;
int count = ity.second;
if ((x == minx || x == maxx) && (y == miny || y == maxy)) {
if (count != 1) return false;
}
else {
if (count != 2 && count != 4) {
return false;
}
}
}
}
return area == (maxx-minx)*(maxy-miny);
}