218. The Skyline Problem [leetcode]

来源:互联网 发布:小程序授权登录源码 编辑:程序博客网 时间:2024/06/05 07:36

题目:

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>加入结果集中。

参考:https://discuss.leetcode.com/topic/14939/my-c-code-using-one-priority-queue-812-ms/2
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 可以很轻松的解决,而且对逻辑思维能力要求也没那么高。很容易就理解了。
提醒自己以后遇到类似的难题,可以先想想能否用分治的方法解决~

加油啦~:)

原创粉丝点击