您的位置:首页 > 其它

The Skyline Problem -- leetcode

2015-09-03 17:03 561 查看
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
. 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.

基本思路:

以buildings的边界为处理单位。以此边界x坐标得到输出点的x坐标,以此边界上的*活动*房屋最高的高度为输出点的y坐标。

在这些点中,去除掉,和上次输出坐标中高度相同的点。

具体做法为,

1. 先取点,房屋左边界和其高度为一个点;房屋右边界和其高度为一个点。

由于左边界和右边界时处理时,要有所区分。 可以将高度一个设置为正,一个设置为负。以示区分。

2. 要将左边界的高度设为负数。原因在于,相同x位置处,要先处理左边界,所以的左边界处理完毕后,再处理该位置处的右边界。

这样,设置为负,在排序时,就能将x位置相同的左边界的点排到前面。同时,也能将此处高度最高的房屋排到前面。

换句话说,在同一x坐标处,优先处理左边界,且优先将该位置处最高的房屋进行处理。

这样做的目的,可以确保,在输出结点时,总能得到当前x坐标处高度最高的房屋。

3. 初一看,此处需要用到优先队列。按高度排序。

但是,priority_queue有个缺点,只能通过pop()删除元素。

而multiset,则满足此处的需求,在*rbegin()处,总能返回最大值。

而erase则能删除指定的元素。

4. 在处理左边界时,将高度存入multiset中。表示此房屋进入活动状态。

处理右边界时,从multiset中删除掉此房屋,表示此房屋处理完毕。它已退出活动状态。 即求当前高度时,不再需要考虑它们了。

此处,存入multiset中,不需要精确指定房屋的编号,只需要插入和删除对应的高度即可。

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> cliffs;
        for (auto b: buildings) {
            cliffs.push_back(make_pair(b[0], -b[2]));
            cliffs.push_back(make_pair(b[1], b[2]));
        }
        sort(cliffs.begin(), cliffs.end());

        vector<pair<int, int>> ans;
        multiset<int> height;
        height.insert(0);
        for (auto edge: cliffs) {
            if (edge.second < 0)
                height.insert(-edge.second);
            else
                height.erase(height.find(edge.second));
            
            const int xi = edge.first;
            const int hi = *height.rbegin();

            if (ans.empty() || ans.back().second != hi)
                ans.push_back(make_pair(xi, hi));
        }
        return ans;
    }
};


在最终达到上面一版时,一共经历好几次改进。

第五版(见上)

与第四版的差别为,

1. 数组cliffs的元素从(x坐标,原数组索引值),改为(x坐标,高度)

好处为,在进入结果集时,只需要判断,与上次高度是否相等,相等则忽略。

在以前版的算法中,需要判断,是为与上一个点的x坐标相同,如果相同,则更新其高度坐标。

而此版中,总能保证,x坐标相同的左边界点在处理时,第一个处理的点(插入height),一点是高度最高的点。(其高度为负数,绝对值最大,排序排最前面)。

2. multiset元素的修改,从值对(高度,索引),修改为只有高度

在旧版中,处理房屋右边界时,是从multiset中,精确删除处理该房屋左边界时插入的值。比较直观好懂。即:处理左边界时,房屋进入活动状态,右边界时,该房屋退出活动状态。

然而如此精确匹配是不必要的。 只需要高度即可。即:

处理左边界时,插入高度值,表示该高度处,增加了一个活动房屋。

处理右边界时,删除一个高度值,表示该高度处,减少一个活动房屋。

这正是multiset特点,同一个值,可以存储多份。

看来旧版中用set就可以了。

3. 先提前插入0进入multiset中,省去了在获取当前高度时,对multiset判空的操作。

第四版

将左边界和右边界统一处理。先进行排序。

去掉了前一版的对右边界的动态排序priority_queue。代价是是引入一个新的vector。

好处是,代码变得更简洁了。

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> cliffs;
        for (int i=0; i<buildings.size(); i++) {
            cliffs.push_back(make_pair(buildings[i][0], -(i+1)));
            cliffs.push_back(make_pair(buildings[i][1], (i+1)));
        }
        sort(cliffs.begin(), cliffs.end());

        vector<pair<int, int>> ans;
        multiset<pair<int, int>> height;
        for (auto edge: cliffs) {
            if (edge.second < 0)
                height.insert(make_pair(buildings[-edge.second-1][2], -edge.second));
            else
                height.erase(make_pair(buildings[edge.second-1][2], edge.second));
            
            const int xi = edge.first;
            const int hi = height.empty() ? 0 : (*height.rbegin()).first;
            
            if (!ans.empty() && ans.back().first == xi)
                ans.back().second = hi;
            else if (ans.empty() || ans.back().second != hi)
                ans.push_back(make_pair(xi, hi));
        }
        return ans;
    }
};


第三版

和第二版相比,引入了multiset。

使用multiset取代原来的一个priority_queue和set。

multiset的 *rbegin(),相当于 priority_queue的 top();

而且multiset还支持指定元素删除。就不需要用set来指示,priority_queue的值是否合法了。

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> ans;
        multiset<pair<int, int>> height;
        priority_queue<pair<int, int>, vector<pair<int,int>>, greater_equal<pair<int, int>>> right;
        int i=0, xi, hi;
        while (i<buildings.size() || !right.empty()) {
            if (i == buildings.size() || (!right.empty() && right.top().first < buildings[i][0])) {
                const int index = right.top().second;
                right.pop();
                height.erase(make_pair(buildings[index][2], index));
                
                hi = height.empty() ? 0 : buildings[(*height.rbegin()).second][2];
                xi = buildings[index][1];
            }
            else {
                const int index = i++;
                height.insert(make_pair(buildings[index][2], index));
                right.push(make_pair(buildings[index][1], index));
                
                hi = buildings[(*height.rbegin()).second][2];
                xi = buildings[index][0];
            }
            
            if (!ans.empty() && ans.back().first == xi)
                ans.back().second = hi;
            else if (ans.empty() || ans.back().second != hi)
                ans.push_back(make_pair(xi, hi));
        }
        return ans;
    }
};


第二版

1. 去掉第一版中的自定义比较函数。 使用c++自带的比较函数就能达到相同目的。

2. 去掉检查建筑的拐角是否和其他建筑有交集。省去了在set中遍历。

总是获取当前活动建筑最高的高度, 作为输出y坐标。

和最终版的已经是同一思路了。

class Solution {
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> ans;
        unordered_set<int> through;
        priority_queue<pair<int, int>> height;
        priority_queue<pair<int, int>, vector<pair<int,int>>, greater_equal<pair<int, int>>> right;
        int i=0, xi, hi;
        while (i<buildings.size() || !right.empty()) {
            if (i == buildings.size() || (!right.empty() && right.top().first < buildings[i][0])) {
                const int index = right.top().second;
                right.pop();
                through.erase(index);
                
                while (!height.empty()) {
                    if (through.count(height.top().second))
                        break;
                    height.pop();
                }
                
                hi = height.empty() ? 0 : buildings[height.top().second][2];
                xi = buildings[index][1];
            }
            else {
                const int index = i++;
                through.insert(index);
                height.push(make_pair(buildings[index][2], index));
                right.push(make_pair(buildings[index][1], index));
                
                hi = buildings[height.top().second][2];
                xi = buildings[index][0];
            }
            
            if (!ans.empty() && ans.back().first == xi)
                ans.back().second = hi;
            else if (ans.empty() || ans.back().second != hi)
                ans.push_back(make_pair(xi, hi));
        }
        return ans;
    }
};


第一版

这一版贴出来,留个纪念。虽然丑漏,毕竟是在没有参考网上资料情况下完全独立完成的。

在leetcode上执行1424ms,时间将最终版(880ms)的一倍。

基本思路,

以边界为处理单位。

处理左边界时,检查左上角,是否和其他建筑有交集,如果有,则抛弃。 否则偿试输出。

处理右边界时,检查右上角,是否和其他建筑有交庥,如果有,则抛弃。否则偿试输出。

用一个优先队列rights,来动态对右边界进行排序。

用一个优先队列heights,动态对高度进行排序。

用一个set (变量名为through),表示当前处于活动中的房屋。

如何判断与当前活动房屋有交集,是通过在set中进行遍历比较完成的。 这就是为啥性能比较差的原因所在。

由于不能对heights中,进行指定元素删除。所以heights中的元素合法不合法,需要借助through来完成。在through中存在的,则为合法值。

class Solution {
    class my_less_comp {
    public:
        bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) {
            return lhs.first < rhs.first;
        }
    };
    class my_great_comp {
    public:
        bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) {
            return lhs.first >= rhs.first;
        }
    };
public:
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<pair<int, int>> ans;
        unordered_set<int> through;
        priority_queue<pair<int, int>, vector<pair<int,int>>, my_less_comp> height;
        priority_queue<pair<int, int>, vector<pair<int,int>>, my_great_comp> right;
        int i=0;
        while (i<buildings.size() || !right.empty()) {
            if (i == buildings.size() || (!right.empty() && right.top().first < buildings[i][0])) {
                int index = right.top().second;
                right.pop();
                through.erase(index);
                for (auto b: through) {
                    if (buildings[b][0]<= buildings[index][1] && buildings[index][1] <= buildings[b][1] &&
                        buildings[index][2] <= buildings[b][2]) {
                        index = -1;
                        break;
                    }
                }
                if (-1 == index)
                    continue;
                
                while (!height.empty()) {
                    if (through.count(height.top().second))
                        break;
                    height.pop();
                }
                
                int hi = height.empty() ? 0 : buildings[height.top().second][2];
                
                if (!ans.empty() && ans.back().first == buildings[index][1])
                    ans.back().second = hi;
                else
                    ans.push_back(make_pair(buildings[index][1], hi));
            }
            else {
                int index = i++;
                through.insert(index);
                height.push(make_pair(buildings[index][2], index));
                right.push(make_pair(buildings[index][1], index));
                for (auto b: through) {
                    if (index != b && buildings[b][0]<= buildings[index][0] && buildings[index][0] <= buildings[b][1] &&
                        buildings[index][2] <= buildings[b][2]) {
                        index = -1;
                        break;
                    }
                }
                if (-1 == index) {
                    continue;
                }
                
                if (!ans.empty() && ans.back().first == buildings[index][0])
                    ans.back().second = buildings[index][2];
                else
                    ans.push_back(make_pair(buildings[index][0], buildings[index][2]));
            }
        }
        return ans;
    }
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: