[LeetCode] 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 are given 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
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], ... ]
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:
- The number of buildings in any input list is guaranteed to be in the range
[0, 10000]
. - The input list is already sorted in ascending order by the left x position
Li
. - The output list must be sorted by the x position.
- There must be no consecutive horizontal lines of equal height in the output skyline. For instance,
[...[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], ...]
Credits:
Special thanks to @stellari for adding this problem, creating these two awesome images and all test cases.
分別將每個線段的左邊節點與右邊節點存到新的vector height中,根據x座標值排序,然後遍歷求拐點。求拐點的時候用一個最大化heap來儲存當前的樓頂高度,遇到左邊節點,就在heap中插入高度資訊,遇到右邊節點就從heap中刪除高度。分別用pre與cur來表示之前的高度與當前的高度,當cur != pre的時候說明出現了拐點。在從heap中刪除元素時要注意,我使用priority_queue來實現,priority_queue並不提供刪除的操作,所以又用了別外一個unordered_map來標記要刪除的元素。在從heap中pop的時候先看有沒有被標記過,如果標記過,就一直pop直到空或都找到沒被標記過的值。別外在排序的時候要注意,如果兩個節點的x座標相同,我們就要考慮節點的其它屬性來排序以避免出現冗餘的答案。且體的規則就是如果都是左節點,就按y座標從大到小排,如果都是右節點,按y座標從小到大排,一個左節點一個右節點,就讓左節點在前。下面是AC的程式碼。
1 class Solution { 2 private: 3 enum NODE_TYPE {LEFT, RIGHT}; 4 struct node { 5 int x, y; 6 NODE_TYPE type; 7 node(int _x, int _y, NODE_TYPE _type) : x(_x), y(_y), type(_type) {} 8 }; 9 10 public: 11 vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { 12 vector<node> height; 13 for (auto &b : buildings) { 14 height.push_back(node(b[0], b[2], LEFT)); 15 height.push_back(node(b[1], b[2], RIGHT)); 16 } 17 sort(height.begin(), height.end(), [](const node &a, const node &b) { 18 if (a.x != b.x) return a.x < b.x; 19 else if (a.type == LEFT && b.type == LEFT) return a.y > b.y; 20 else if (a.type == RIGHT && b.type == RIGHT) return a.y < b.y; 21 else return a.type == LEFT; 22 }); 23 24 priority_queue<int> heap; 25 unordered_map<int, int> mp; 26 heap.push(0); 27 vector<pair<int, int>> res; 28 int pre = 0, cur = 0; 29 for (auto &h : height) { 30 if (h.type == LEFT) { 31 heap.push(h.y); 32 } else { 33 ++mp[h.y]; 34 while (!heap.empty() && mp[heap.top()] > 0) { 35 --mp[heap.top()]; 36 heap.pop(); 37 } 38 } 39 cur = heap.top(); 40 if (cur != pre) { 41 res.push_back({h.x, cur}); 42 pre = cur; 43 } 44 } 45 return res; 46 } 47 };
使用一些技巧可以大大減少編碼的複雜度,priority_queue並沒有提供erase操作,但是multiset提供了,而且multiset內的資料是按BST排好序的。在區分左右節點時,我之前自己建了一個結構體,用一個屬性type來標記。這裡可以用一個小技巧,那就是把左邊節點的高度值設成負數,右邊節點的高度值是正數,這樣我們就不用額外的屬性,直接用pair<int, int>就可以儲存了。而且對其排序,發現pair預設的排序規則就已經滿足要求了。
1 class Solution { 2 public: 3 vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { 4 vector<pair<int, int>> height; 5 for (auto &b : buildings) { 6 height.push_back({b[0], -b[2]}); 7 height.push_back({b[1], b[2]}); 8 } 9 sort(height.begin(), height.end()); 10 multiset<int> heap; 11 heap.insert(0); 12 vector<pair<int, int>> res; 13 int pre = 0, cur = 0; 14 for (auto &h : height) { 15 if (h.second < 0) { 16 heap.insert(-h.second); 17 } else { 18 heap.erase(heap.find(h.second)); 19 } 20 cur = *heap.rbegin(); 21 if (cur != pre) { 22 res.push_back({h.first, cur}); 23 pre = cur; 24 } 25 } 26 return res; 27 } 28 };
LintCode上也有一道跟這道一樣,不過只是輸出的結果不同。