📜  天际线问题 | 2套(1)

📅  最后修改于: 2023-12-03 14:51:42.953000             🧑  作者: Mango

天际线问题 | 2套

简介

天际线问题(Skyline Problem)是指给定一系列建筑物的轮廓线,求解出从远处观察时,能够看到的建筑物轮廓线。这个问题可以用于城市规划,建筑物设计等领域。

解法一

一种常见的解法是使用扫描线算法。我们将建筑物按照从左到右的顺序排序,在扫描线向右移动的过程中,记录下当前能够看到的所有建筑物的最高高度。如果有建筑物的起点在当前扫描线的右侧,这个建筑物就会成为可见的建筑物,并将其高度加入到一个优先队列中。如果有建筑物的终点在当前扫描线的右侧,这个建筑物就会离开可见建筑物的集合。如果有建筑物比当前最高的建筑物更高,那么这个建筑物将成为当前可见建筑物中的一员,而原本能够看到的较低的建筑物就会被遮挡。

代码示例
from heapq import *
from typing import List


def getSkyline(buildings: List[List[int]]) -> List[List[int]]:
    # 初始化扫描线
    events = [(L, -H, R) for L, R, H in buildings]
    events += list({(R, 0, 0) for _, R, _ in buildings})
    events.sort()

    # 初始化优先队列
    pq = [(0, float('inf'))]
    res = []
    for x, h, r in events:
        while x >= pq[0][1]:
            heappop(pq)
        if h:
            heappush(pq, (h, r))
        if res[-1][1] != -pq[0][0]:
            res.append([x, -pq[0][0]])

    return res[1:]
解法二

另一种解法是使用线段树。我们将建筑物按照从左到右的顺序排序,并将每个建筑物的起始位置和终止位置分别作为区间的左端点和右端点,区间高度设为建筑物高度。在进行线段树的构建和查询时,我们使用区间最大值作为区间高度,在向上递归时,我们需要同时维护区间的最大值和最小值。如果区间的最大值等于当前扫描线的高度,那么这个区间就是可见的。

代码示例
from typing import List


class SegmentTreeNode:
    def __init__(self, l: int, r: int):
        self.l, self.r = l, r
        self.max_height = 0
        self.min_height = 0
        self.left = None
        self.right = None


class Solution:
    def getSkyline(self, buildings: List[List[int]]) -> List[List[int]]:
        n = len(buildings)
        if n == 0:
            return []
        xs = sorted(list(set([L for L, _, _ in buildings] + [R for _, R, _ in buildings])))
        x_dict = {xs[i]: i for i in range(len(xs))}
        root = self.buildSegmentTree(0, len(xs) - 1, x_dict)
        for L, R, H in buildings:
            self.modifySegmentTree(root, x_dict[L], x_dict[R] - 1, H)
        res = []
        for i in range(len(xs) - 1):
            max_height = self.querySegmentTree(root, i)
            if len(res) == 0 or max_height != res[-1][1]:
                res.append([xs[i], max_height])
        return res

    def buildSegmentTree(self, l: int, r: int, x_dict: dict) -> SegmentTreeNode:
        root = SegmentTreeNode(l, r)
        if l < r:
            mid = (l + r) // 2
            root.left = self.buildSegmentTree(l, mid, x_dict)
            root.right = self.buildSegmentTree(mid + 1, r, x_dict)
        return root

    def modifySegmentTree(self, root: SegmentTreeNode, l: int, r: int, height: int):
        if root.l == l and root.r == r:
            root.max_height = max(height, root.max_height)
            root.min_height = min(height, root.min_height)
        else:
            mid = (root.l + root.r) // 2
            if r <= mid:
                self.modifySegmentTree(root.left, l, r, height)
            elif l > mid:
                self.modifySegmentTree(root.right, l, r, height)
            else:
                self.modifySegmentTree(root.left, l, mid, height)
                self.modifySegmentTree(root.right, mid + 1, r, height)
            root.max_height = max(root.left.max_height, root.right.max_height)
            root.min_height = min(root.left.min_height, root.right.min_height)

    def querySegmentTree(self, root: SegmentTreeNode, i: int) -> int:
        if root.l == root.r:
            return root.max_height
        mid = (root.l + root.r) // 2
        if i <= mid:
            return self.querySegmentTree(root.left, i)
        else:
            if root.left.max_height >= root.right.min_height:
                return self.querySegmentTree(root.left, i)
            else:
                return self.querySegmentTree(root.right, i)