2021-09-07218. 天际线问题

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

每个建筑物的几何信息由数组 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], ...]

 

输入: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]]

顺序扫描端点
我们看看一个比较极端的测试用例:[[0, 2147483647, 2147483647]]
对于这个用例如果采用上面的方法则需要开一个非常大的数组,并且需要顺序扫描两次这个大数组,但实际上中间的大段数组和扫描都是无效的没有起到作用的,所以很容易想到,如果只扫描一些关键的结点,例如建筑的左右端点,即可避免大量的无效操作,但又衍生出了新的问题:如何维护区间内的最大高度信息

具体来说:

当遇到左端点,我们可以直接与当前最高建筑高度比较,判断是否是转折点
当遇到右端点,则需判断是否是转折点,若是转折点,那么高度下降到多少?
显然问题的难点在于右端点的处理,我的做法是设置一个优先队列,用于存储 [右端点坐标,建筑高度],具体做法如下:

顺序扫描建筑(题目保证左端点单调递增)
先检测优先队列中是否有小于当前建筑左端点的右端点
若有,则取出,根据当前高度判断此右端点的高度是否是天际线
若是天际线,则在优先队列中查找最高的建筑高度,该最高建筑高度即新的天际线高度
但有可能天际线高度是好几个同高度的建筑组成的,所以还要再增加一个判断,取出后天际线下降了的才是转折点
若不是天际线,那无需做任何操作
判断左端点是否大于最大建筑高度,若大于,那么当前建筑高度将成为新的天际线高度,更新相关变量(注意左端点非严格单增,需判断是新增转折点还是更新旧的转折点)
遍历完建筑后还需处理队列中的右端点,方法与遍历循环中的相同

解题思路

顺序扫描坐标

一个简单的思路是:我们设置一个大的数组,数组下标对应横坐标 x ,然后遍历各个建筑物,更新建筑物对应区间的最大建筑高度,

获取最大右端点长度为24,设置长度为25的数组,初始化为 0
遍历第一个建筑物:范围是 [2,9][2,9],高度为 10,将数组中下标为 [2,9][2,9] 范围内的值更新为 10
遍历第二个建筑物……
遍历第三个建筑物:范围是 [5,12][5,12],高度为 12,将数组中下标为 [5,12][5,12] 范围内的值更新为 12,但此时 [5,7][5,7] 范围内已有高度为 15 的建筑,所以 范围 [5,7][5,7] 不更新
以此类推直到遍历完所有建筑
此时数组内的值如上图B所示,我们只需扫描数组找到红点标出的转折点即可很容易的得到答案

时间复杂度: O(n \times m)O(n×m), nn 为建筑数量,mm 为平均建筑宽度
空间复杂度: O(m)O(m)
遗憾的是这是一道困难题,nn 的取值范围为 [1, 10^4][1,10 
4
 ], 而 m 的取值范围为 [1, 2^{31} - 1][1,2 
31 −1],显然时间不允许这样做

class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        s = []
        ans = []
        h = 0
        for left, right, height in buildings:
            while s and s[0][0] < left:
                r, rh = heapq.heappop(s)
                if rh == h:
                    rh = max(s, key=lambda val: val[1])[1] if s else 0
                    if rh != h:
                        h = rh
                        ans.append([r, h])
            if height > h:
                h = height
                # 避免左端点重复的问题
                if ans and left == ans[-1][0]:
                    ans[-1][1] = h
                else:
                    ans.append([left, h])
            heapq.heappush(s, [right, height])
        while s:
            r, rh = heapq.heappop(s)
            if rh == h:
                rh = max(s, key=lambda val: val[1])[1] if s else 0
                if rh != h:
                    h = rh
                    ans.append([r, h])
        return ans
class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        s =[]
        ans =[]
        h =0
        for left, right,height in buildings:
            while s and s[0][0] <left:
                r,rh = heqpq.heappop(s)
                if rh ==h:
                    rh =mas(s, key =lambda val:val[1])[1] if s else 0
                    if rh!=h
                        h =rh
                        ans.append([r,h])
            if height> h:
                h = height
                if ans and left ==ans[-1][0]:
                    ans[-1][1] =h
                else:
                    ans.append([left,h])
            heapq.heappush(s,[right,height])
        while s:
            r,rh = heapq.heappop(s)
            if rh ==h:
                rh = max(s, key = lambda val:val[1])[1] if s else 0
                if rh != h:
                    h =rh
                    ans.append([r,h])
        return ans

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

强化学习曾小健

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值