We are given a list of (axis-aligned) `rectangles`. Each `rectangle[i] = [x1, y1, x2, y2] `, where (x1, y1) are the coordinates of the bottom-left corner, and (x2, y2) are the coordinates of the top-right corner of the `i`th rectangle.
Find the total area covered by all rectangles
in the plane. Since the answer may be too large, return it modulo 10^9 + 7.
Example 1:
Input: [[0,0,2,2],[1,0,2,3],[1,0,3,1]]
Output: 6
Explanation: As illustrated in the picture.
Example 2:
Input: [[0,0,1000000000,1000000000]]
Output: 49
Explanation: The answer is 10^18 modulo (10^9 + 7), which is (10^9)^2 = (-7)^2 = 49.
Note:
1 <= rectangles.length <= 200
rectanges[i].length = 4
0 <= rectangles[i][j] <= 10^9
- The total area covered by all rectangles will never exceed
2^63 - 1
and thus will fit in a 64-bit signed integer.
這道題是之前那道 [Rectangle Area](http://www.cnblogs.com/grandyang/p/4563153.html) 的拓展,那道題只有兩個矩形重疊,而這道題有多個矩形可能同時重疊,整體難度一下就上來了,那么通過將所有矩形面積加起來再減去重疊區域的方法這里就不太適用了,因為多個矩形在同一區域重疊的話,都減去重疊面積是會錯的,還得把多減的補回來,相當的麻煩。這里我們需要換一種解題的思路,不能一股腦兒的把所有的矩形都加起來,而是應該利用微積分的思想,將重疊在一起的區域拆分成一個個的小矩形,分別累加面積,因為這里的矩形是不會旋轉的,所以是可以正常拆分的。思路有了,新建一個二維數組 all 來保存所有的矩形,然后遍歷給定的矩形數組,對於每個遍歷到的數組,調用一個子函數,將當前的矩形加入 all 中。下面主要來看一下這個子函數 helper 該如何實現?首先要明白這個函數的作用是將當前矩形加入 all 數組中,而且用的是遞歸的思路,所以要傳入一個 start 變量,表示當前和 all 數組中正在比較的矩形的 index,這樣在開始的時候,檢查一下若 start 大於等於 all 數組長度,表示已經檢測完 all 中所有的矩形了,將當前矩形加入 all 數組,並返回即可。否則的話則取出 start 位置上的矩形 rec,此時就要判斷當前要加入的矩形和這個 rec 矩形是否有重疊,這在 LeetCode 中有專門一道題是考察這個的 [Rectangle Overlap](https://www.cnblogs.com/grandyang/p/10367583.html),這里用的就是那道題的判斷方法,假如判斷出當前矩形 cur 和矩形 rec 沒有交集,就直接對 all 數組中下一個矩形調用遞歸函數,並返回即可。假如有重疊的話,就稍微麻煩一點,由於重疊的部位不同,所以需要分情況討論一下,參見下圖所示:
對於一個矩形 Rectangle,若有另外一個矩形跟它有重疊的話,可以將重疊區域分為四個部分,如上圖的 Case1,Case2,Case3,Case4 所示,非重疊部分一定會落在一個或多個區域中,則可以把這些拆開的小矩形全部加入到矩形數組 all 中。仔細觀察上圖可以發現,對於將矩形 cur 拆分的情況可以分為下面四種:
- 落入區間1,條件為 cur[0] < rec[0],產生的新矩形的兩個頂點為 {cur[0], cur[1], rec[0], cur[3]}。
- 落入區間2,條件為 cur[2] > rec[2],產生的新矩形的兩個頂點為 {rec[2], cur[1], cur[2], cur[3]}。
- 落入區間3,條件為 cur[1] < rec[1],產生的新矩形的兩個頂點為 {max(rec[0], cur[0]), cur[1], min(rec[2], cur[2]), rec[1]}。
- 落入區間4,條件為 cur[3] > rec[3],產生的新矩形的兩個頂點為 {max(rec[0], cur[0]), rec[3], min(rec[2], cur[2]), cur[3]}。
這樣操作下來的話,整個所有的區域都被拆分成了很多個小矩形,每個矩形之間都不會有重復,最后只要分別計算每個小矩形的面積,並累加起來就是最終的結果了,參見代碼如下:
解法一:
class Solution {
public:
int rectangleArea(vector<vector<int>>& rectangles) {
long res = 0, M = 1e9 + 7;
vector<vector<int>> all;
for (auto rectangle : rectangles) {
helper(all, rectangle, 0);
}
for (auto &a : all) {
res = (res + (long)(a[2] - a[0]) * (long)(a[3] - a[1])) % M;
}
return res;
}
void helper(vector<vector<int>>& all, vector<int> cur, int start) {
if (start >= all.size()) {
all.push_back(cur); return;
}
auto rec = all[start];
if (cur[2] <= rec[0] || cur[3] <= rec[1] || cur[0] >= rec[2] || cur[1] >= rec[3]) {
helper(all, cur, start + 1); return;
}
if (cur[0] < rec[0]) {
helper(all, {cur[0], cur[1], rec[0], cur[3]}, start + 1);
}
if (cur[2] > rec[2]) {
helper(all, {rec[2], cur[1], cur[2], cur[3]}, start + 1);
}
if (cur[1] < rec[1]) {
helper(all, {max(rec[0], cur[0]), cur[1], min(rec[2], cur[2]), rec[1]}, start + 1);
}
if (cur[3] > rec[3]) {
helper(all, {max(rec[0], cur[0]), rec[3], min(rec[2], cur[2]), cur[3]}, start + 1);
}
}
};
下面這種解法更是利用了微積分的原理,把x軸長度為1當作一個步長,然后計算每一列有多少個連續的區間,每個連續區間又有多少個小正方形,題目中給的例子每一個列都只有一個連續區間,但事實上是可以有很多個的,只要算出了每一列 1x1 小正方形的個數,將所有列都累加起來,就是整個區域的面積。這里求每列上小正方形個數的方法非常的 tricky,博主也不知道該怎么講解,大致就是要求同一列上每個連續區間中的小正方形個數,再累加起來。對於每個矩形起始的橫坐標,映射較低的y值到1,較高的y值到 -1,對於結束位置的橫坐標,剛好反過來一下,映射較低的y值到 -1,較高的y值到1。這種機制跟之前那道 [The Skyline Problem](http://www.cnblogs.com/grandyang/p/4534586.html) 有些異曲同工之妙,都還是為了計算高度差服務的。要搞清楚這道題的核心思想,不是一件容易的事,博主的建議是就拿題目中給的例子帶入到下面的代碼中,一步一步執行,並分析結果,是能夠初步的了解解題思路的,若實在有理解上的問題,博主可以進一步寫些講解,參見代碼如下:
解法二:
class Solution {
public:
int rectangleArea(vector<vector<int>>& rectangles) {
long res = 0, pre_x = 0, height = 0, start = 0, cnt = 0, M = 1e9 + 7;
map<int, vector<pair<int, int>>> groupMap;
map<int, int> cntMap;
for (auto &a : rectangles) {
groupMap[a[0]].push_back({a[1], 1});
groupMap[a[0]].push_back({a[3], -1});
groupMap[a[2]].push_back({a[1], -1});
groupMap[a[2]].push_back({a[3], 1});
}
for (auto &group : groupMap) {
res = (res + (group.first - pre_x) * height) % M;
for (auto &a : group.second) {
cntMap[a.first] += a.second;
}
height = 0, start = 0, cnt = 0;
for (auto &a : cntMap) {
if (cnt == 0) start = a.first;
cnt += a.second;
if (cnt == 0) height += a.first - start;
}
pre_x = group.first;
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/850
類似題目:
參考資料:
https://leetcode.com/problems/rectangle-area-ii/
https://leetcode.com/problems/rectangle-area-ii/discuss/138028/Clean-Recursive-Solution-Java
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)