218. 天际线问题

218. 天际线问题

题目描述

城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回由这些建筑物形成的 天际线

每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:

  • lefti 是第 i 座建筑物左边缘的 x 坐标。

  • righti 是第 i 座建筑物右边缘的 x 坐标。

  • heighti 是第 i 座建筑物的高度。

天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],...] ,并按x 坐标 进行 排序关键点是水平线段的左端点。列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。

注意: 输出天际线中不得有连续的相同高度的水平线。例如 [...[2 3], [4 5], [7 5], [11 5], [12 7]...] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]

示例 1:


输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:
图 A 显示输入的所有建筑物的位置和高度,
图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。

示例 2:

输入:buildings = [[0,2,3],[2,5,3]]
输出:[[0,3],[5,0]]

提示:

  • 1 <= buildings.length <= 10^4

  • 0 <= lefti < righti <= 2^31 - 1

  • 1 <= heighti <= 2^31 - 1

  • buildingslefti 非递减排序


题解:

扫描线。

这题关键点是:高度发生变化时,会产生关键点。具体来说:建筑物左边高度升高时,会产生关键点;右边高度降低时,会产生关键点。

我们将建筑物拆分成两部分,记为 {l, -h}{r, h},然后将拆分后的建筑物按照以下规则排序:

  • 若坐标相等,按照高度从小到大排序;
  • 若坐标不等,按照坐标从小到大排序;

这样排序是因为:如果位置都不相等,左边高度变高时,肯定产生关键点,而右边高度降低时,也会产生关键点。而在位置相等的地方,左边最高的是关键点,右边最低的是关键点,从而在第一次遍历到时就会记录结果。

从左往右遍历拆分后的数组,使用 multiset 记录高度值(很关键),遇到左边部分直接插入 multiset 中,并且当前高度比 multiset 最大高度大时,出现关键点,记录结果;遇到右边部分从 multiset 中删除,并且当前高度比 multiset 最大高度大时,出现关键点,记录结果。

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

额外空间复杂度: O ( n ) O(n) O(n)

class Solution {
    using PII = pair<int, int>;
public:
    vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
        int n = buildings.size();
        vector<PII> build;
        for ( int i = 0; i < n; ++i ) {
            build.push_back( {buildings[i][0], -buildings[i][2]} );
            build.push_back( {buildings[i][1],  buildings[i][2]} );
        }
        sort( build.begin(), build.end() );
        multiset<int> height;
        height.insert( 0 );
        vector<vector<int>> ret;
        for ( auto& it : build ) {
            if ( it.second < 0 ) {
                if ( -it.second > *height.rbegin() )
                    ret.push_back( {it.first, -it.second} );
                height.insert( -it.second );
            } else {
                height.erase( height.find(it.second) );
                if ( it.second > *height.rbegin() )
                    ret.push_back( {it.first, *height.rbegin()} );
            }
        }
        return ret;
    }
};
/*
时间:32ms,击败:89.09%
内存:14.4MB,击败:78.31%
*/

当然,也可以记录上一个转折点,稍微优化一下:

class Solution {
    using PII = pair<int, int>;
public:
    vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
        int n = buildings.size();
        vector<PII> build;
        for ( int i = 0; i < n; ++i ) {
            build.push_back( {buildings[i][0], -buildings[i][2]} );
            build.push_back( {buildings[i][1],  buildings[i][2]} );
        }
        sort( build.begin(), build.end() );
        multiset<int> height;
        height.insert( 0 );
        vector<vector<int>> ret;
        int pre = 0, now;
        for ( auto& it : build ) {
            if ( it.second < 0 ) height.insert( -it.second );
            else height.erase( height.find(it.second) );
            now = *height.rbegin();
            if ( now != pre ) {
                ret.push_back( {it.first, now} );
                pre = now;
            }
        }
        return ret;
    }
};
/*
时间:24ms,击败:99.03%
内存:14.4MB,击败:78.06%
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值