这道题Brute Force的做法不难想,我们把矩形分解成1x1的小矩形,用一个二维数组表示对应的单位矩形是否出现过,然后算总面积即可。但是显而易见这个方法时间复杂度过于高,并不适用。
但是这个方法可以提供一个很好的思路,我们可以把输入的点压缩一下来优化一下时间复杂度。比如说如果x轴上的点出现了[2, 5, 30, 200, 3200],我们可以将其映射到[0, 1, 2, 3, 4]的区间,这样我们仍然是算有多少个1x1个小矩形,但是算面积的时候,再把对应[1, N]区间的数map回原坐标计算面积即可。假设输入有N个矩形,时间复杂度O(N^3),空间复杂度O(N^2), 代码如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
int rectangleArea(vector<vector<int>>& rectangles) { | |
const int MOD = 1000000007; | |
unordered_set<int> setX, setY; | |
for (const auto& rectangle : rectangles) | |
{ | |
int x1 = rectangle[0], x2 = rectangle[2], y1 = rectangle[1], y2 = rectangle[3]; | |
setX.insert(x1); | |
setX.insert(x2); | |
setY.insert(y1); | |
setY.insert(y2); | |
} | |
vector<int> xs, ys; | |
for (const auto& x : setX)xs.push_back(x); | |
for (const auto& y : setY)ys.push_back(y); | |
sort(xs.begin(), xs.end()); | |
sort(ys.begin(), ys.end()); | |
int lenX = xs.size(), lenY = ys.size(); | |
unordered_map<int, int> mapX, mapY; | |
for (int i = 0; i < lenX; ++i)mapX[xs[i]] = i; | |
for (int i = 0; i < lenY; ++i)mapY[ys[i]] = i; | |
vector<vector<bool>> grid(lenY - 1, vector<bool>(lenX - 1, false)); | |
for (const auto& rect : rectangles) | |
{ | |
int minX = mapX[rect[0]], maxX = mapX[rect[2]], minY = mapY[rect[1]], maxY = mapY[rect[3]]; | |
for (int i = minY; i < maxY; ++i) | |
for (int j = minX; j < maxX; ++j) | |
grid[i][j] = true; | |
} | |
long area = 0; | |
for (int i = 0; i < lenY - 1; ++i) | |
{ | |
for (int j = 0; j < lenX - 1; ++j) | |
{ | |
if (grid[i][j]) | |
{ | |
area += static_cast<long>(ys[i + 1] - ys[i]) * (xs[j + 1] - xs[j]); | |
area %= MOD; | |
} | |
} | |
} | |
return area; | |
} | |
}; |
除此之外,如果我们想象一根平行于x轴的直线从最底端向上扫,我们可以得到所有矩形在不同y坐标下在x轴的映射。我们可以根据每个单位长度y下,矩形在x轴上映射的长度的和来算出总的矩形的面积。所以现在的问题是,如果算出矩形在x轴上的映射,Segment Tree可以帮助我们解决这类区间问题。
首先我们的关心的是range,所以叶节点也是range,也就是说我们的叶节点是类似[1, 2], [2, 3]这种而不是之前有的题目我们会存的[1, 1],[2, 2]。其次就是我们在每一个node上存什么,这里有多种思路。首先我们要知道的是每个区间有多长被cover了,记作coveredRange。但是我们不能直接存这个,比如我们插入了两次[1, 3],之后又删除了[1, 3]一次,这时候[1, 3]仍然是被完全覆盖的,但是只存coveredRange的话我们是没法检测这种情况的。所以我们转而存当前区间被完全覆盖的次数,因为我们需要区间更新,所以我们采用lazy propagation的实现方式,具体查看上面的文章。更新的话按部就班,查询的话如果当前区间被完全覆盖了我们直接return对应的的range,否则递归查询左右两个子区间。这里我们仍然要用index compression来压缩输入x和y的范围,我们可以去重也可以不去重,不会影响结果。时间复杂度O(N log N),空间复杂度O(N),代码如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Node | |
{ | |
public: | |
int cnt;//# of times the range is fully covered | |
int start, end, mark;//lazy propagation | |
Node* left, *right; | |
Node(int s, int e) | |
{ | |
cnt = 0; | |
mark = 0; | |
start = s; | |
end = e; | |
left = nullptr; | |
right = nullptr; | |
} | |
Node* getLeft() | |
{ | |
if (start + 1 >= end) | |
return nullptr; | |
if (left)return left; | |
int mid = start + (end - start) / 2; | |
left = new Node(start, mid); | |
left->cnt = cnt; | |
return left; | |
} | |
Node* getRight() | |
{ | |
if (start + 1 >= end) | |
return nullptr; | |
if (right)return right; | |
int mid = start + (end - start) / 2; | |
right = new Node(mid, end); | |
right->cnt = cnt; | |
return right; | |
} | |
void update(int s, int e, int val) | |
{ | |
if (mark)propagate(); | |
if (s <= start && e >= end) | |
{ | |
if (start + 1 < end) | |
{ | |
getLeft()->mark += val; | |
getRight()->mark += val; | |
} | |
cnt += val; | |
return; | |
} | |
else if (s >= end || e <= start) | |
return; | |
else | |
{ | |
getLeft()->update(s, e, val); | |
getRight()->update(s, e, val); | |
cnt = min(getLeft()->cnt, getRight()->cnt); | |
} | |
} | |
int query(int s, int e, vector<int>& map) | |
{ | |
if (mark)propagate(); | |
if (start + 1 >= end && !cnt)return 0; | |
if (s <= start && e >= end && cnt) | |
return map[end] - map[start]; | |
else if (s >= end || e <= start) | |
return 0; | |
else | |
return getLeft()->query(s, e, map) + getRight()->query(s, e, map); | |
} | |
private: | |
void propagate() | |
{ | |
if (start + 1 < end) | |
{ | |
getLeft()->mark += mark; | |
getRight()->mark += mark; | |
} | |
cnt += mark; | |
mark = 0; | |
} | |
}; | |
class Solution { | |
public: | |
int rectangleArea(vector<vector<int>>& rectangles) { | |
if (rectangles.empty())return 0; | |
const int MOD = 1000000007; | |
vector<vector<int>> events; | |
vector<int> xs; | |
for (const auto& rectangle : rectangles) | |
{ | |
int x1 = rectangle[0], x2 = rectangle[2], y1 = rectangle[1], y2 = rectangle[3]; | |
events.push_back({y1, 1, x1, x2}); | |
events.push_back({ y2, -1, x1, x2 }); | |
xs.push_back(x1); | |
xs.push_back(x2); | |
} | |
auto comp = [](const vector<int>& lhs, const vector<int>& rhs) | |
{ | |
return lhs[0] == rhs[0] ? lhs[1] > rhs[1]: lhs[0] < rhs[0]; | |
}; | |
sort(events.begin(), events.end(), comp); | |
sort(xs.begin(), xs.end()); | |
int len = events.size(); | |
//compress input size by mapping it to [0, len - 1] | |
unordered_map<int, int> xMap; | |
for (int i = 0; i < len; ++i)xMap[xs[i]] = i; | |
long area = 0; | |
int last_y = events[0][0]; | |
Node* root = new Node(0, len - 1); | |
root->update(xMap[events[0][2]], xMap[events[0][3]], events[0][1]); | |
for (int i = 1; i < len; ++i) | |
{ | |
auto event = events[i]; | |
int y = event[0]; | |
area += static_cast<long>(y - last_y) * root->query(0, len - 1, xs); | |
area %= MOD; | |
root->update(xMap[event[2]], xMap[event[3]], event[1]); | |
last_y = y; | |
} | |
return area; | |
} | |
}; |
官方给出的segment tree是另外一种存的方式,每个节点存了两个变量:
- 当前区间被覆盖的长度,coveredRange
- 输入区间能被分解成当前区间的次数cnt。比如[1, 4],假设可以分解为[1, 3]和[3, 4],这两个区间对应线段树里的两个node,我们把对应node的cnt数加1,而[1, 3]的子区间[1, 2]和[2, 3]我们不管。也就是说这里的cnt和上面的不太一样,不再具有父区间cnt的改变一定会影响到子区间的性质,反之亦然。这里每个node对应的cnt是相互独立的。
那么我们查询和更新的时候就可以根据这两个变量来进行操作。时间复杂度空间复杂度相同,代码如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Node | |
{ | |
public: | |
int cnt;//The total # of times that existing ranges can be splitted into this piece | |
int coveredRange; | |
int start, end; | |
Node* left, *right; | |
Node(int s, int e) | |
{ | |
cnt = 0; | |
coveredRange = 0; | |
start = s; | |
end = e; | |
left = nullptr; | |
right = nullptr; | |
} | |
Node* getLeft() | |
{ | |
if (start + 1 >= end) | |
return nullptr; | |
if (left)return left; | |
int mid = start + (end - start) / 2; | |
left = new Node(start, mid); | |
return left; | |
} | |
Node* getRight() | |
{ | |
if (start + 1 >= end) | |
return nullptr; | |
if (right)return right; | |
int mid = start + (end - start) / 2; | |
right = new Node(mid, end); | |
return right; | |
} | |
void update(int s, int e, int val, vector<int>& map) | |
{ | |
if (s <= start && e >= end) | |
cnt += val; | |
else if (s >= end || e <= start) | |
return; | |
else | |
{ | |
getLeft()->update(s, e, val, map); | |
getRight()->update(s, e, val, map); | |
} | |
coveredRange = cnt ? map[end] - map[start] : (start + 1 >= end? 0 : getLeft()->coveredRange + getRight()->coveredRange); | |
} | |
}; | |
class Solution { | |
public: | |
int rectangleArea(vector<vector<int>>& rectangles) { | |
if (rectangles.empty())return 0; | |
const int MOD = 1000000007; | |
vector<vector<int>> events; | |
vector<int> xs; | |
for (const auto& rectangle : rectangles) | |
{ | |
int x1 = rectangle[0], x2 = rectangle[2], y1 = rectangle[1], y2 = rectangle[3]; | |
events.push_back({y1, 1, x1, x2}); | |
events.push_back({ y2, -1, x1, x2 }); | |
xs.push_back(x1); | |
xs.push_back(x2); | |
} | |
auto comp = [](const vector<int>& lhs, const vector<int>& rhs) | |
{ | |
return lhs[0] == rhs[0] ? lhs[1] > rhs[1]: lhs[0] < rhs[0]; | |
}; | |
sort(events.begin(), events.end(), comp); | |
sort(xs.begin(), xs.end()); | |
int len = events.size(); | |
//compress input size by mapping it to [0, len - 1] | |
unordered_map<int, int> xMap; | |
for (int i = 0; i < len; ++i)xMap[xs[i]] = i; | |
long area = 0; | |
int last_y = events[0][0]; | |
Node* root = new Node(0, len - 1); | |
root->update(xMap[events[0][2]], xMap[events[0][3]], events[0][1], xs); | |
for (int i = 1; i < len; ++i) | |
{ | |
auto event = events[i]; | |
int y = event[0]; | |
area += static_cast<long>(y - last_y) * root->coveredRange; | |
area %= MOD; | |
root->update(xMap[event[2]], xMap[event[3]], event[1], xs); | |
last_y = y; | |
} | |
return area; | |
} | |
}; |
No comments:
Post a Comment