【算法】The Skyline Problem

Difficulty:Hard

Description

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

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

Solution

我的思路

(自己的方法,比较直观,没什么技巧,目测能过测试,但 Time Limit Exceeded)

首先把被其他建筑覆盖的建筑删掉,把等高且有重叠部分的建筑合并成一个建筑。(这一步是为了等下在构建skyline时不被用>还是>=困扰太多,同时排除掉出现在同一直线的多个点)

对每一个建筑,都找出跟它有重叠部分(紧贴着的也算)的建筑。对此我创建一个邻接表来作一个无向图,有连线的两个建筑即为有重叠部分的。这样在构建skyline时,对于一个建筑,只需考虑跟它有重叠部分的建筑即可,同时个人认为也方便确定skyline右下角([x, 0])这一类的点。

怎么构建skyline是关键。对于一个矩形建筑,我们只需考虑它的左上顶点、右下顶点和右边的边上能否打点(无需考虑上边,因为假设A建筑的右边和B建筑的上边相交出了该点,若把A右边的点和B上边的点都当成pair算进去,只会产生重复。只考虑右边是为了不用排序的方便)

  • 对于每一个建筑A,在跟它有重叠的其他建筑中(利用刚才构建的无向图),若有一个建筑B,使A.Li >= B.LiA.Hi < B.Hi,则A的左上顶点不能打点;
  • 同理,若A.Ri < B.Ri,则A的右下顶点不能打点;
  • 在所有A.Ri < B.RiA.Hi > B.Hi的B中,选Hi最大的B建筑,打点的坐标为[A.Ri, B.Hi]

代码

目测能过测试,但 Time Limit Exceeded。有空再切磋大牛的解答。

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> skyline;
        vector<vector<int>> graph;
        graph.resize(buildings.size());
        int currBuild, nextBuild;

        // 把相同或被覆盖的建筑删掉
        for (auto p1 = buildings.begin(); p1 != buildings.end(); ++p1) {
            for (auto p2 = p1 + 1; p2 != buildings.end();) {
                if ((*p2)[0] == (*p1)[0] && (*p2)[1] >= (*p1)[1] &&
                    (*p2)[2] >= (*p1)[2]) {                 // p2覆盖了p1
                    p1 = buildings.erase(p1);
                    p2 = p1 + 1;
                    continue;
                }
                else if ((*p2)[2] == (*p1)[2] && (*p2)[1] >= (*p1)[1] &&
                    (*p2)[0] <= (*p1)[1]) {             // 需要把p1和p2合并
                    vector<int> temp = { (*p1)[0], (*p2)[1], (*p1)[2] };
                    buildings.erase(p1);
                    p1 = buildings.erase(p2);
                    p1 = buildings.insert(p1, temp);
                    p2 = p1 + 1;
                    continue;
                }
                else if ((*p2)[1] <= (*p1)[1] && (*p2)[2] <= (*p1)[2]) {  // p1覆盖了p2
                    buildings.erase(p2);
                    continue;
                }
                ++p2;
            }
        }

        // 创建一个无向图,建筑与建筑间有重叠的(紧挨着也算),连线
        for (currBuild = 0; currBuild < buildings.size() - 1; ++currBuild) {
            for (nextBuild = currBuild + 1; nextBuild < buildings.size(); ++nextBuild) {
                if (buildings[nextBuild][0] > buildings[currBuild][1]) break;
                graph[currBuild].push_back(nextBuild);
                graph[nextBuild].push_back(currBuild);
            }
        }

        // 构建skyline
        bool topleftFlag = true;  // 建筑左上角是否能打点,true为可以打点
        bool downrightFlag = true;  // 右下角是否能打点
        bool rFlag = true;  // 右边能否打点
        int hmax = 0;
        for (currBuild = 0; currBuild < buildings.size();
            ++currBuild, rFlag = downrightFlag = topleftFlag = true, hmax = 0) {
            for (int relatedBuild : graph[currBuild]) {
                if (topleftFlag &&
                    buildings[relatedBuild][0] <= buildings[currBuild][0] &&
                    buildings[relatedBuild][2] > buildings[currBuild][2]) {
                    topleftFlag = false;
                }
                if (downrightFlag &&
                    buildings[relatedBuild][1] > buildings[currBuild][1]) {
                    downrightFlag = false;
                }
                if (rFlag &&
                    buildings[relatedBuild][1] >= buildings[currBuild][1] &&
                    buildings[relatedBuild][2] >= buildings[currBuild][2]) {
                    rFlag = false;
                }
                if (rFlag &&
                    buildings[relatedBuild][1] > buildings[currBuild][1] &&
                    buildings[relatedBuild][2] < buildings[currBuild][2] &&
                    buildings[relatedBuild][2] > hmax) {
                    hmax = buildings[relatedBuild][2];
                }
            }
            if (topleftFlag) {
                skyline.push_back(
                    make_pair(buildings[currBuild][0], buildings[currBuild][2]));
            }
            if (downrightFlag) {
                bool f = true;
                for (auto temp : skyline) {
                    if (temp.first == buildings[currBuild][1]) f = false;
                }
                if (f)
                    skyline.push_back(make_pair(buildings[currBuild][1], 0));
            }
            if (rFlag && hmax > 0) {
                skyline.push_back(make_pair(buildings[currBuild][1], hmax));
            }
        }
        return skyline;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值