题目链接: https://leetcode.com/problems/the-skyline-problem/
A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you aregiven the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).
The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi]
, where Li
and Ri
are the x coordinates of the left and right edge of the ith building, respectively, and Hi
is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX
, 0 < Hi ≤ INT_MAX
, and Ri - Li > 0
. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.
For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ]
.
The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ]
that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.
For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]
.
Notes:
[0, 10000]
.Li
.[...[2 3], [4 5], [7 5], [11 5], [12 7]...]
is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...]
思路: skyline也是一道经典的sweep line的算法题了, 最初的想法是比较复杂, 就是利用一个优先队列, 和一个multiset, 先以x轴进行排序, 然后扫描一遍数组, 每次将以end point和height为一对放到优先队列中去, 并且将高度单独放到multiset中, 在每次遍历到一个新的点时, 先查看优先队列, 是否有元素的end point比当前元素的start point小, 如果是的话就将其出队列, 并且在multiset中也将其height值删除. 然后如果被删除的点高度比剩余的multiset中的最大值大的话, 说明这个值是一个边际点, 添加到结果集合中. 每次遍历一个新元素之前重复此操作.
将所有的起点遍历完之后, 优先队列中会剩余一些end point没有出队列, 再依次将其出队列, 如果出队列之后剩余的点的高度都比被删除的点高度小, 则出队列的点也是边际点.
这种算法比较复杂, 另外也发现了一个比较坑爹的地方, 就是set和multiset的upper_bound和lower_bound, upper_bound是第一个比val大的值, 而lower_bound是第一个大于等于val的值, 而我一直都以为lower_bound是第一个比val小的值, 这个名字起的很有误导性, 我也在另外某人的一篇博客中看到和我犯了相同错误的人.
后来感觉我的算法太过复杂, 去看了别人的思路, 发现一个非常巧妙的方法, 就是将起点连同高度与终点连同高度各作为一个pair都统一保存到一个数组中, 然后进行排序, 为区分起点和终点将起点的高度设为负值. 然后在处理这个包含起点和终点的数组, 如果是起点, 就将其高度放到multiset中, 如果是终点就将在遍历起点入集合的高度值删除. 然后再看添加和或者删除前后的multiset中最大值是否发生改变, 如果发生改变了说明这个点是边际点.
各自代码如下:
class Solution { public: vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { auto cmp = [](pair<int, int> a, pair<int, int> b){ return a.first==b.first?a.second>b.second:a.first>b.first;}; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> que(cmp); multiset<int> st; vector<pair<int, int>> result; auto func = [](vector<int> a, vector<int> b){ return a[0]==b[0]?a[2]>b[2]:a[0]<b[0];}; sort(buildings.begin(), buildings.end(), func); for(auto val: buildings) { while(!que.empty() && que.top().first < val[0]) { auto tem = que.top(); que.pop(); st.erase(st.find(tem.second)); if(que.empty()) result.push_back(make_pair(tem.first, 0)); else if(st.lower_bound(tem.second) == st.end()) result.push_back(make_pair(tem.first, *st.rbegin())); } if(st.empty() || st.lower_bound(val[2]) == st.end()) result.push_back(make_pair(val[0], val[2])); que.push(make_pair(val[1], val[2])); st.insert(val[2]); } while(!que.empty()) { auto tem = que.top(); que.pop(); st.erase(st.find(tem.second)); if(que.empty()) result.push_back(make_pair(tem.first, 0)); else if(st.lower_bound(tem.second) == st.end()) result.push_back(make_pair(tem.first, *st.rbegin())); } return result; } };
class Solution { public: vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { multiset<int> st; vector<pair<int, int>> edges; vector<pair<int, int>> result; for(auto val: buildings) { int left=val[0], right=val[1], height=val[2]; edges.push_back(make_pair(left, -height)); edges.push_back(make_pair(right, height)); } sort(edges.begin(), edges.end()); st.insert(0); int pre = 0, cur; for(auto val: edges) { int pos = val.first; if(val.second < 0) st.insert(-val.second); else st.erase(st.find(val.second)); cur= *st.rbegin(); if(pre != cur) result.push_back(make_pair(pos, pre=cur)); } return result; } };第二种参考: https://leetcode.com/discuss/83498/recommend-beginners-implementation-detailed-explanation