1. 程式人生 > >[LeetCode] The Skyline Problem

[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).

Buildings Skyline Contour

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_MAX0 < 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:

  • 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上也有一道跟這道一樣,不過只是輸出的結果不同。