218. The Skyline Problem [leetcode]

題目:

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], ...]

想了半天沒想出來,感覺真的是一道燒腦題。

自己的思考能力、邏輯能力也還是很有待提升。

這道題的原理就是,尋找有效的拐點。該點滿足不被其他任何building覆蓋,或者爲一個building右部的拐點。

一個building具有的覆蓋力從左邊起持續到右邊結束。當右邊結束的時候,該building對有部分的所有building都拾取了覆蓋力。

因此,我們先將所有的building的左邊線段和右邊線段分開,將位置和高度信息以pair的形式存入vector中。再將所有線段按照位置的先後排序。

然後遍歷這些線段。當遍歷到開始(building的左邊線段)線段時,將其加入multiset容器m中,當遍歷到結束線段時,將容器m中對與之應的開始線段刪除。m中存的這些線段實際上就代表了當前仍然具有覆蓋力的building。

我們在插入新線段判斷,該線段是否高於m中最高的線段,若高於,則說明當前的這個building的左上點不會被任何其他building覆蓋,爲有效的點,因此加入結果集中。

在刪除線段時,若刪除的線段高於剩餘所有線段的高度。則說明改線段的右邊會出現有效拐點。將該拐點加入結果集中。


class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> res;
        if (buildings.size() == 0) return res;
        vector<pair<int,int>> points;
        for (int i = 0; i < buildings.size(); i++) {
            //make sure that when two point's have the same height and position, the start point will go before end point.
            points.push_back(make_pair(buildings[i][0], -buildings[i][2]));
            points.push_back(make_pair(buildings[i][1], buildings[i][2]));
        }
        sort(points.begin(), points.end());
        multiset<int> live{0};
        int pre = 0;
        for (int i = 0; i < points.size(); i++) {
            if (points[i].second > 0) { // met the end point.
                live.erase(live.find(points[i].second));
                int maxH = *live.rbegin();
                if (points[i].second > maxH)
                    res.push_back(make_pair(points[i].first, maxH));
            }
            else {
                int maxH = *live.rbegin();
                if (maxH < -points[i].second) res.push_back(make_pair(points[i].first,-points[i].second));
                live.insert(-points[i].second);
            }     
        }
        return res;
    }
};

還有一種與該方法非常相似的,不過比較複雜,它沒有將每個building的左右線段拆開。而是用一個priority_queue來裝builidng,裏面的building按照右x的優先級排列。並且利用一個multiset記錄當前queue中的building的高度。當queue中的building失去覆蓋力時(即其右x小於 當前的building的左x),則pop出該building,並刪除multiset中對應的高度值。

code:


方法二:
在討論區還看到好多種類原理的解法:
下面這個解法使用到了priority_queue來存儲還未被處理完的buildings的高度h和右x,這些buildings 都是右邊還未處理判斷的building。他們按照高度排序。高度相同的x越大,優先級越前。
逐個處理buildings。
若當前訪問的building b1的x大於當前queue中的最大高度building b2的右x:
首先pop出b2.
首先將b1加入queue中。並判斷queue中其他building的h是否大於b1的h,若無,則將<b1的左x,b1的h>加入結果集中。
其次則將queue中的所有x小於b2 的x 的buildings pop出,直到遇到一個x值大於b2的x的building,記錄下將該builidng的高度h2,將<b1的x,h2> 加入結果集中。該building的<左x,h>

若當前訪問的building b1的x小於等於當前queue中的最大高度building b2的右x:
將b1加入queue中。並判斷queue中其他building的h是否大於b1的h,若無,則將<b1的左x,b1的h>加入結果集中。

code:

方法三:
分治歸併發  Divide and Conquer

這種方法是最直觀的也是最好理解的。需要考慮的細節也比較少一些,雖然代碼稍微長一些,但是想起來不那麼燒腦,邏輯更爲清晰。
就是通過把逐漸兩個子skyline合併成一個父skyline即可最後求出總的skyline。
每次合併:
設置兩個指針a,b分別指向skyline1的第一個key point,和skyline2的第一個key point。設置個數組變量pre【2】分別記錄skyline1和skyline2中前咦個被處理的keypoint的高度(該高度會影響下一個加入結果集的key point 的高度)。
比較a和b中x的大小。若a的x小於b,選取h = max(pre【1】,a的高度), 將<a的x, h > 放入結果中。
若b的x小於a,選取h = max(pre【0】,b的高度), 將<b的x, h > 放入結果中。
注意,在放之前檢查h是否與當前結果集最後一個key point的高度相同,若相同則選擇不放入。(可以通過下圖推理)
就這樣一直合併到a和b都指向結尾。


其中黃點代表的是當前需要處理的節點。也就是x位置靠前的,紅點代表,另一個skyline中上上一個被處理的keypoint。

code:
class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> res;
        vector<vector<pair<int,int>>> s;
        if (buildings.size() == 0) return res;
        for (auto i : buildings) {
            vector<pair<int,int>> kps;
            kps.push_back(make_pair(i[0],i[2]));
            kps.push_back(make_pair(i[1], 0));
            s.push_back(kps);
        }
        return getSkyline(s);
    }
    vector<pair<int,int>> merge(vector<pair<int,int>> s1, vector<pair<int,int>> s2){
        int i1 = 0, i2 = 0;
        int pre[2] = {0};
        vector<pair<int,int>> res;
        while (i1 < s1.size() && i2 < s2.size()) {
            int h = 0, x = 0;
            if (s1[i1].first < s2[i2].first) {
                h = max(pre[1], s1[i1].second);
                x = s1[i1].first;
                pre[0] = s1[i1++].second;
            }
            else if (s1[i1].first > s2[i2].first){
                h = max(pre[0], s2[i2].second);
                x = s2[i2].first;
                pre[1] = s2[i2++].second;
            }
            else { //注意判斷當將x重合的情況
                x = s1[i1].first;
                h = s1[i1].second >= s2[i2].second ? s1[i1].second : s2[i2].second;
                pre[0] = s1[i1++].second;
                pre[1] = s2[i2++].second;
            }
            
            if (res.size() > 0 && res.rbegin()->second == h) continue;
            
            res.push_back(make_pair(x, h));
            
        }
        while (i1 < s1.size()) {
             res.push_back(s1[i1++]);
        }
        while (i2 < s2.size()) {
            res.push_back(s2[i2++]);
        }
        return res;
    }
    
    vector<pair<int,int>> getSkyline(vector<vector<pair<int,int>>> s) {
        if (s.size() == 1) return s[0];
        return merge(getSkyline(vector<vector<pair<int,int>>>(s.begin(), s.begin() + s.size() / 2)),
                     getSkyline(vector<vector<pair<int,int>>>(s.begin() + s.size() / 2, s.end())));
    }
    
};




總結:
這道題想了很久,嘗試着寫了幾次代碼,但是最後都把自己寫死了。其實自己最初的思路是跟方法二非常類似的。但是不清楚如何去判斷什麼樣的右x能加入結果集中。還是自己的邏輯思維欠缺。
以及,發現其實很多看似很難的問題用divide and conquer 可以很輕鬆的解決,而且對邏輯思維能力要求也沒那麼高。很容易就理解了。
提醒自己以後遇到類似的難題,可以先想想能否用分治的方法解決~

加油啦~:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章