【队列】力扣218:天际线问题
给定建筑物的起止位置和高度,返回建筑物轮廓(天际线)的拐点。
示例:
输入: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 中的红点表示输出列表中的关键点。
输入是一个二维整数数组,表示每个建筑物的 [左端, 右端, 高度];输出是一个二维整数数组,表示每个拐点的横纵坐标。
题目中的关键点可以分为三类:
-
由于从左往右时有高度上升的矩形,这类点会在后一个矩形的【左端点】处
-
由于从左往右时有高度下降的矩形,这类点会在前一个矩形的【右边界】处
- 特殊地,右边没有连续矩形,这类点会在最后一个矩形的【右端点】处
可以想到一个暴力的算法:O(n) 地枚举建筑的每一个边缘作为关键点的横坐标,同时 O(n) 地检查每一座建筑是否「包含该横坐标」,找到最大高度,即为该关键点的纵坐标。该算法的时间复杂度是 O(n^2),需要进行优化。
那么可以用「优先队列」来优化寻找最大高度的时间:在从左到右枚举横坐标的过程中,实时地更新该优先队列即可。无论何时,优先队列的队首元素即为最大高度。为了维护优先队列,需要使用「延迟删除」的技巧,即无需每次横坐标改变就立刻将优先队列中所有不符合条件的元素都删除,而只需要保证优先队列的队首元素「包含该横坐标」即可。
在实际代码中可以进行一个优化。因为每一座建筑的左边缘信息只被用作加入优先队列时的依据,当其加入优先队列后,只需要用到其高度信息(对最大高度有贡献)以及其右边缘信息(弹出优先队列的依据),因此只需要在优先队列中保存这两个元素即可。
具体来讲:
-
设置一个优先队列,用于储存 [右端点,建筑高度]
-
顺序扫描建筑(题目保证左端点单调递增)
-
先检测优先队列中是否有小于建筑左端点的右端点(这里是想检测第二类点)
-
若有,则从优先队列中弹出右边界最小的一个(为什么是有边界最小的?这是因为一个矩形之所以能形成第二类关键点,是因为存在比它矮且右端点更靠右的矩形,因此要从右端点最靠左的矩形看起,逐步弹出矩阵并判断它是否能形成第二类关键点)
-
判断弹出的高度和当前最高高度 h 是否相等,若相等则继续(注意这里的最高高度只能说是当前最高高度,因为后面会随弹出的矩形高度变小。那么为什么小于当前最高高度的都不行呢?这时因为如果有【在当前最高矩形前面且[右端点大于最高矩形左端点](加上这一条件是因为它没有在上一轮被弹出)】的矩形,那么只会形成第一类点)
-
判断新的最高高度 rh 与之前最高高度 h 是否相同,若不同则继续(也就是说这里要找被弹出的最后一个高度为 h 的矩形)
- 令新的最高高度为 rh(注意这里改变了最高高度h),并将转折点 [r, h] 加入 ans ,其中 r 为弹出的右端点
-
-
-
-
判断左端点是否大于最大建筑高度,若大于,那么将 [left, height] 加入 ans 并更新当前最高高度
-
遍历完建筑后还需处理队列中的右端点
class Solution:
def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
s = [] # 优先队列,用于储存 [右端点,建筑高度]
h = 0
ans = []
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 x:x[1])[1] if s else 0
if rh != h:
h = rh
ans.append([r, h])
if height > h:
h = height
if ans and ans[-1][0] == left:
ans[-1][1] = h
else:
ans.append([left, height])
heapq.heappush(s, [right, height])
while s:
r, rh = heapq.heappop(s)
if rh == h:
rh = max(s, key = lambda x:x[1])[1] if s else 0
if rh != h:
h = rh
ans.append([r, h])
return ans
作者:20185944
链接:https://leetcode.cn/problems/the-skyline-problem/solution/by-20185944-m9lo/
时间复杂度:O(nlogn),其中 n 为建筑数量。每座建筑至多只需要入队与出队一次,单次时间复杂度为 O(logn)。
空间复杂度:O(n)。数组 boundaries 和优先队列的空间占用均为 O(n)。
观察规律直接破解
来源:并查集
左横坐标 i 对应右横坐标 x:pa[i] = x
左横坐标 i 对应当前高度 h:xh[i] = y
class Solution:
def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
all_points = {x for xy in buildings for x in xy[:2]} # 获取不重复的所有断点
all_points = list(all_points) # 转换为列表
all_points.sort() # 所有端点升序排列
n = len(all_points)
point2i = {p: i for i, p in enumerate(all_points)}# 记录排序后的所有 端点值-下标 对
pa = [i for i in range(n)] # 下标 i 的 pa 就是 i 当前城市线高度的右端点
xh = [0] * n # 当前点 x 的最大高度
buildings.sort(key = lambda elem:elem[2], reverse = True) # 输入数据按高度降序排列
for x, y, h in buildings:
left, right = point2i[x], point2i[y]
while left < right:
while left != pa[left]:
left = pa[left]
if left < right:
pa[left], xh[left] = right, h
left += 1
return [[all_points[i], xh[i]] for i in range(n) if i ==0 or xh[i - 1] != xh[i]]
作者:nothing-n5
链接:https://leetcode.cn/problems/the-skyline-problem/solution/bing-cha-ji-by-nothing-n5-sepr/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?