[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.
這道題一開啟又是圖又是這麼長的題目的,看起來感覺應該是一道相當複雜的題,但是做完之後發現也就那麼回事,雖然我不會做,是學習的別人的解法。這道求天際線的題目應該算是比較新穎的題,要是非要在之前的題目中找一道類似的題,也就只有 Merge Intervals 合併區間了吧,但是與那題不同的是,這道題不是求被合併成的空間,而是求輪廓線的一些關鍵的轉折點,這就比較複雜了,我們通過仔細觀察題目中給的那個例子可以發現,要求的紅點都跟每個小區間的左右區間點有密切的關係,而且進一步發現除了每一個封閉區間的最右邊的結束點是樓的右邊界點,其餘的都是左邊界點,而且每個紅點的縱座標都是當前重合處的最高樓的高度,但是在右邊界的那個樓的就不算了。在網上搜了很多帖子,發現網友Brian Gordon的帖子圖文並茂,什麼動畫漸變啊,橫向掃描啊,簡直叼到沒朋友啊,但是叼到極致後就懶的一句一句的去讀了,這裡博主還是講解另一位網友百草園的部落格吧。這裡用到了multiset資料結構,其好處在於其中的元素是按堆排好序的,插入新元素進去還是有序的,而且執行刪除元素也可方便的將元素刪掉。這裡為了區分左右邊界,將左邊界的高度存為負數,建立左邊界和負高度的pair,再建立右邊界和高度的pair,存入陣列中,都存進去了以後,給陣列按照左邊界排序,這樣我們就可以按順序來處理那些關鍵的節點了。我們要在multiset中放入一個0,這樣在某個沒有和其他建築重疊的右邊界上,我們就可以將封閉點存入結果res中。下面我們按順序遍歷這些關鍵節點,如果遇到高度為負值的pair,說明是左邊界,那麼將正高度加入multiset中,然後取出此時集合中最高的高度,即最後一個數字,然後看是否跟pre相同,這裡的pre是上一個狀態的高度,初始化為0,所以第一個左邊界的高度絕對不為0,所以肯定會存入結果res中。接下來如果碰到了一個更高的樓的左邊界的話,新高度存入multiset的話會排在最後面,那麼此時cur取來也跟pre不同,可以將新的左邊界點加入結果res。第三個點遇到綠色建築的左邊界點時,由於其高度低於紅色的樓,所以cur取出來還是紅色樓的高度,跟pre相同,直接跳過。下面遇到紅色樓的右邊界,此時我們首先將紅色樓的高度從multiset中刪除,那麼此時cur取出的綠色樓的高度就是最高啦,跟pre不同,則可以將紅樓的右邊界橫座標和綠樓的高度組成pair加到結果res中,這樣就成功的找到我們需要的拐點啦,後面都是這樣類似的情況。當某個右邊界點沒有跟任何樓重疊的話,刪掉當前的高度,那麼multiset中就只剩0了,所以跟當前的右邊界橫座標組成pair就是封閉點啦,具體實現參看程式碼如下:
class Solution { public: vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { vector<pair<int, int>> h, res; multiset<int> m; int pre = 0, cur = 0; for (auto &a : buildings) { h.push_back({a[0], -a[2]}); h.push_back({a[1], a[2]}); } sort(h.begin(), h.end()); m.insert(0); for (auto &a : h) { if (a.second < 0) m.insert(-a.second); else m.erase(m.find(a.second)); cur = *m.rbegin(); if (cur != pre) { res.push_back({a.first, cur}); pre = cur; } } return res; } };
參考資料: