【困难】力扣算法题解析LeetCode218:天际线问题

题目详情

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

每个建筑物的几何信息由数组 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 <= 10^4
  • 0 <= lefti < righti <= 2^31 - 1
  • 1 <= heighti <= 2^31 - 1
  • buildingslefti 非递减排序

解题思路

本题需要高效地计算建筑物的天际线关键点,核心在于扫描线算法延迟删除优化。以下是详细步骤:

  1. 事件生成

    • 每个建筑物生成两个事件:开始事件(左边缘,高度取负)和结束事件(右边缘,高度取正)。
    • 例如,建筑 [left, right, height] 生成事件 [left, -height][right, height]
  2. 事件排序

    • x 坐标升序排序,x 相同时按高度升序(保证开始事件优先处理,且同一位置高建筑先处理)。
  3. 扫描线处理

    • 使用最大堆维护当前活跃建筑的最大高度。
    • 使用 delayed 字典记录待删除的高度及次数(延迟删除优化)。
    • 初始化堆中加入 0(表示地面高度)。
  4. 关键点生成

    • 遍历排序后的事件列表,对每个 x 位置处理所有事件:
      • 开始事件:将高度绝对值加入堆。
      • 结束事件:将高度加入 delayed 字典(计数+1)。
    • 清理堆顶无效高度(在 delayed 中标记过的)。
    • 获取当前有效最大高度,若与前一个关键点高度不同,则记录关键点 [x, currMax]
  5. 亮点

    • 延迟删除:避免每次删除操作都调整堆,仅在堆顶元素无效时删除,均摊复杂度低。
    • 事件批处理:同一 x 位置的事件统一处理,确保每个位置只生成一个关键点。

时间复杂度O(n log n),其中 n 是建筑数量(事件数 2n,堆操作 O(log n))。
空间复杂度O(n),存储事件、堆和延迟删除字典。


代码实现(Java版)

class Solution {
    public List<List<Integer>> getSkyline(int[][] buildings) {
        // 创建事件列表:每个事件是 [x, height]
        List<int[]> events = new ArrayList<>();
        for (int[] building : buildings) {
            int left = building[0], right = building[1], height = building[2];
            events.add(new int[]{left, -height}); // 开始事件:高度取负
            events.add(new int[]{right, height});  // 结束事件:高度取正
        }
        
        // 事件排序:优先按x坐标升序;x相同时按高度升序(保证开始事件优先,且高建筑优先)
        Collections.sort(events, (a, b) -> {
            if (a[0] != b[0]) {
                return Integer.compare(a[0], b[0]); // x坐标升序
            }
            return Integer.compare(a[1], b[1]); // x相同则按事件高度升序
        });
        
        // 最大堆(维护当前活跃建筑的高度)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        maxHeap.offer(0); // 初始地面高度0
        // 延迟删除字典:记录已被移除但仍在堆中的高度及其出现次数
        Map<Integer, Integer> delayed = new HashMap<>();
        
        List<List<Integer>> ans = new ArrayList<>();
        int prevMax = 0; // 记录上一次的最大高度
        
        int i = 0;
        while (i < events.size()) {
            int currentX = events.get(i)[0];
            // 处理同一x位置的所有事件
            while (i < events.size() && events.get(i)[0] == currentX) {
                int h = events.get(i)[1];
                if (h < 0) { // 开始事件:高度取绝对值加入堆
                    maxHeap.offer(-h);
                } else { // 结束事件:记录延迟删除
                    delayed.put(h, delayed.getOrDefault(h, 0) + 1);
                }
                i++;
            }
            
            // 清理堆顶无效高度(已标记延迟删除的)
            while (!maxHeap.isEmpty()) {
                int top = maxHeap.peek();
                if (delayed.containsKey(top)) {
                    // 堆顶高度已被标记删除,移除堆顶并更新延迟字典
                    maxHeap.poll();
                    int count = delayed.get(top);
                    if (count == 1) {
                        delayed.remove(top);
                    } else {
                        delayed.put(top, count - 1);
                    }
                } else {
                    break; // 堆顶有效,退出清理
                }
            }
            
            // 获取当前有效最大高度
            int currMax = maxHeap.isEmpty() ? 0 : maxHeap.peek();
            // 若最大高度变化,则记录关键点
            if (currMax != prevMax) {
                ans.add(Arrays.asList(currentX, currMax));
                prevMax = currMax;
            }
        }
        
        return ans;
    }
}

代码说明

  1. 事件生成与排序

    • 每个建筑生成两个事件(开始和结束),开始事件高度取负以便区分。
    • 事件按 x 升序排序,x 相同时按事件高度升序(确保高建筑开始事件优先处理)。
  2. 最大堆与延迟删除

    • 最大堆维护当前扫描线穿过的建筑高度。
    • delayed 字典记录已结束建筑的高度及次数,避免立即调整堆。
  3. 关键点生成逻辑

    • 同一 x 位置的事件批量处理,更新堆和延迟字典。
    • 清理堆顶无效高度后,若当前最大高度变化,则记录关键点 [x, currMax]
  4. 边界处理

    • 初始地面高度 0 确保堆不为空。
    • 结束时堆中仅剩 0 时,自动生成 [x,0] 关键点。

提交详情(执行用时、内存消耗)

在这里插入图片描述

我的名片👇👇👇👇👇👇👇👇👇👇👇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

达文汐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值