leetcode
leetcode 201 ~ 250
天际线问题

天际线问题

难度:

标签:

题目描述

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

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

  • lefti 是第 i 座建筑物左边缘的 x 坐标。
  • righti 是第 i 座建筑物右边缘的 x 坐标。
  • heighti 是第 i 座建筑物的高度。

你可以假设所有的建筑都是完美的长方形,在高度为 0 的绝对平坦的表面上。

天际线 应该表示为由 “关键点” 组成的列表,格式 [[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 <= 104
  • 0 <= lefti < righti <= 231 - 1
  • 1 <= heighti <= 231 - 1
  • buildingslefti 非递减排序

代码结果

运行时间: 51 ms, 内存: 20.5 MB


// 思路:
// 1. 使用 Java Stream API 处理建筑物数据和事件。
// 2. 使用相同的逻辑:将建筑物边缘作为事件,按 x 坐标排序。
// 3. 使用优先队列维护当前的最大高度,并生成关键点。
 
import java.util.*;
import java.util.stream.Collectors;
 
public class SkylineStream {
    public List<int[]> getSkyline(int[][] buildings) {
        List<int[]> events = Arrays.stream(buildings)
            .flatMap(building -> Stream.of(new int[]{building[0], -building[2]}, new int[]{building[1], building[2]}))
            .sorted((a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0])
            .collect(Collectors.toList());
 
        PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
        pq.offer(0);
        List<int[]> result = new ArrayList<>();
        int prevHeight = 0;
 
        for (int[] event : events) {
            if (event[1] < 0) {
                pq.offer(-event[1]);
            } else {
                pq.remove(event[1]);
            }
            int currHeight = pq.peek();
            if (currHeight != prevHeight) {
                result.add(new int[]{event[0], currHeight});
                prevHeight = currHeight;
            }
        }
        return result;
    }
}
 

解释

方法:

此题解采用分治和延迟传播的线段树解决天际线问题。首先,将所有建筑物的左右端点收集并去重,对这些端点进行排序和离散化,映射到连续的整数索引上。之后,使用线段树来维护每个区间的最大高度。对于每个建筑物,将其高度更新到线段树中对应的区间上。更新操作中,如果当前线段树节点的区间完全被建筑物覆盖,则使用延迟更新策略标记最大高度。最后,遍历所有的端点,查询对应的最大高度,如果与前一个高度不同,则该点是天际线的一个转折点。这样可以得到所有转折点,即城市的天际线。

时间复杂度:

O(n log m)

空间复杂度:

O(m)

代码细节讲解

🦆
为什么选择使用线段树而不是其他数据结构来解决天际线问题?
线段树是处理区间查询和区间更新问题的有效数据结构。在天际线问题中,需要频繁地对建筑物覆盖的区间进行高度更新(区间更新),并在最终输出时查询每个位置的最大高度(区间查询)。线段树可以在对数时间复杂度内完成这些操作,保证整体算法的效率。此外,线段树支持延迟更新机制,这对于批量更新区间数据特别有用,可以避免不必要的重复计算,提高处理速度。而其他数据结构,如数组或简单的列表,处理这种类型的动态区间问题时,可能会导致过高的时间复杂度,不适合处理大规模数据。
🦆
在使用线段树处理天际线问题时,延迟更新机制的具体作用是什么?
延迟更新机制是线段树优化处理区间更新操作的一种方法。在天际线问题中,当需要将某一区间内所有位置的高度更新到一个新的高度时,如果直接逐个更新,效率会很低。延迟更新允许我们将更新操作标记在某个节点上,而不立即将这个更新应用到所有子节点。只有在必要进行具体查询或进一步的区间更新影响到这些子节点时,才将标记下放,更新子节点的值。这种机制减少了不必要的计算量,特别是在频繁更新区间的情况下,能显著提高效率。
🦆
题解中提到的离散化端点的步骤是如何帮助线段树有效处理区间的?
离散化端点是将问题中涉及的所有端点(如建筑物的左右边界)映射到一组连续的整数索引上。这一步骤对线段树处理区间尤为关键,因为线段树是基于连续整数区间来构建和操作的。通过离散化,我们可以把实际的坐标转换为较小的、连续的索引范围,这样就可以使用线段树高效地管理和查询每个端点的最大高度。此外,离散化可以减少线段树所需处理的数据量,因为实际的坐标可能非常分散和广泛,直接使用这些坐标作为线段树的索引会导致树的规模过大,从而影响效率。

相关问题

掉落的方块

在二维平面上的 x 轴上,放置着一些方块。

给你一个二维整数数组 positions ,其中 positions[i] = [lefti, sideLengthi] 表示:第 i 个方块边长为 sideLengthi ,其左侧边与 x 轴上坐标点 lefti 对齐。

每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。

在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度

返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。

 

示例 1:

输入:positions = [[1,2],[2,3],[6,1]]
输出:[2,5,5]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 2 。
第 2 个方块掉落后,最高的堆叠由方块 1 和 2 组成,堆叠的最高高度为 5 。
第 3 个方块掉落后,最高的堆叠仍然由方块 1 和 2 组成,堆叠的最高高度为 5 。
因此,返回 [2, 5, 5] 作为答案。

示例 2:

输入:positions = [[100,100],[200,100]]
输出:[100,100]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 100 。
第 2 个方块掉落后,最高的堆叠可以由方块 1 组成也可以由方块 2 组成,堆叠的最高高度为 100 。
因此,返回 [100, 100] 作为答案。
注意,方块 2 擦过方块 1 的右侧边,但不会算作在方块 1 上着陆。

 

提示:

  • 1 <= positions.length <= 1000
  • 1 <= lefti <= 108
  • 1 <= sideLengthi <= 106