[H扫描线] lc218. 天际线问题(扫描线求轮廓+边界情况+好题+算法技巧)

1. 题目来源

链接:218. 天际线问题

题解:

2. 题目解析

有点难。

扫描线问题,还是一个求轮廓的扫描线问题…故不需要线段树维护区间查询(求周长、面积时hi使用线段树)。

从左向右扫,找到每个区间段中的高度最高的线段即可。可参考三叶姐的图:【宫水三叶】扫描线算法基本思路 & 优先队列维护当前最大高度

由于本题细节很多,代码很精巧。需要讨论的边界情况很多,才能将几个不同的情况采用相同的处理方式,所以单看代码而言,一种操作的背后其实对应了多种情况,都是需要具体情况具体分析的。请参考:T-SHLoRk 题解:LeetCode 218. The Skyline Problem


关注矩形上边上的两个点,称为该线段的入点、出点,也是扫描线问题的基础。

如果扫描线扫到入点,说明该矩形进入扫描线区间中,如果扫到出点,说明该矩形就要从扫描线区间中出去了。

需要从扫描线维护的区间中找到高度最高的线段作为顶部轮廓线,将左端点 ( x , y ) (x,y) (x,y)加入答案中即可。

需要维护每个顶部轮廓的高度,并且支持排序、插入、删除指定高度。由于本题需要删除指定高度,所以不能使用优先队列来维护高度,但可以使用 multiset 来维护,删除时要使用 find(x),而不能直接用 x

x 下标来排序,顺序扫描每个点,就相当于用 x=k 的直线来扫描每个矩形,multiset 维护顶部轮廓的高度。

  • 遇见左端点,即入点时,如果它的高度是大于所有已有高度的,说明来了个最高的矩形,该点和该高度加入答案,再将这个矩形高度加入 multiset 中。
  • 遇见右端点,即出点时,说明这个矩形要从扫描线中删除,multiset 首先将这个矩形的高度删除,在相同的 x 点处,可能有相同的出点,甚至有入点,以及与其高度相同的出点、入点等。

建议看题解:T-SHLoRk 题解:LeetCode 218. The Skyline Problem,本题好恶心啊,不想往后分析了。

自己研究的这个数据有点意思:[[0,2,1],[0,1,2],[0,1,3]],包含了入点相同,出点相同,两个情况:

  • 相同位置的进点,枚举顺序应先枚举最高的进点。当进点向内进的时候,从高往低进,如果大于当前的最高点,那么就加入答案,否则从低往高进,进一个加一个出错。
  • 相同位置的出点,枚举顺序应先枚举最低位置的出点。 当出点 x=1 外出的时候,从低往高出,只有最高点出去后,最高点下面的那个保留的最低点才被加入答案中。
  • 出点位置刚好有入点的话,先枚举入点再枚举出点。否则出点先出,答案会加入一个比当前位置更低的入点,显然错误。

建议复看的时候多看几遍吧,相同位置的三种情况分析:
在这里插入图片描述


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


情况很多,代码很精巧:

class Solution {
public:
    vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
        vector<vector<int>> res;
        vector<pair<int, int>> points;
        multiset<int> height;

        for (auto &b : buildings) {
            points.push_back({b[0], -b[2]});    // 左端点,入点。同高度入点从高到低排
            points.push_back({b[1], b[2]});     // 右端点,出点。同高度出点从低到高
        }

        sort(points.begin(), points.end());

        height.insert(0);                       // 加入 x 轴这个高度,因为最后落点会落到 x 轴
        for (auto &p : points) {
            int x = p.first, h = abs(p.second);
            if (p.second < 0) {                 // 左端点,入点
                if (h > *height.rbegin()) res.push_back({x, h});    // 高度最高,加入答案,反向迭代器
                height.insert(h);
            } else {                            // 右端点,出点
                height.erase(height.find(h));   // 删去该线段高度
                if (h > *height.rbegin()) res.push_back({x, *height.rbegin()});  // 删掉后的最高一条线段加入答案
            }
        }
        
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值