LeetCode 2015. 每段建筑物的平均高度

2015. 每段建筑物的平均高度

一条完全笔直的街道由一条数字线表示。街道上有建筑物,由二维整数阵列 buildings 表示,其中 buildings[i] = [starti, endi, heighti]。这意味着在 半封闭的位置[starti,endi) 有一座高度为 heighti 的建筑。
你想用 最少 数量的非重叠 部分 来 描述 街道上建筑物的高度。街道可以用2D整数数组 street 来表示,其中 street[j] = [leftj, rightj, averagej] 描述了道路的 半封闭区域 [leftj, rightj) ,该段中建筑物的 平均 高度为 averagej 。

  • 例如,如果 buildings = [[1,5,2],[3,10,4]] , street = [[1,3,2],[3,5,3],[5,10,4]] 可以表示街道,因为:
    • 从 1 到 3 ,只有第一栋建筑的平均高度为 2 / 1 = 2 。
    • 从 3 到 5 ,第一和第二栋建筑的平均高度均为 (2+4) / 2 = 3
    • 从 5 到 10 ,只有第二栋建筑的平均高度为 4 / 1 = 4 。

给定 buildings ,返回如上所述的二维整数矩阵 street ( 不包括 街道上没有建筑物的任何区域)。您可以按 任何顺序 返回数组。
n 个元素的 平均值 是 n 个元素除以 n 的 总和 (整数除法)。
半闭合段 [a, b) 是点 a 和 b 之间的数字线的截面,包括 点 a ,不包括 点 b 。

示例1:

输入: buildings = [[1,4,2],[3,9,4]]
输出: [[1,3,2],[3,4,3],[4,9,4]]
解释:
从 1 到 3 ,只有第一栋建筑的平均高度为 2 / 1 = 2。
从 3 到 4 ,第一和第二栋建筑的平均高度均为(2+4)/ 2 = 3。
从 4 到 9 ,只有第二栋建筑的平均高度为 4 / 1 = 4。

示例 2:

输入: buildings = [[1,3,2],[2,5,3],[2,8,3]]
输出: [[1,3,2],[3,8,3]]
解释:
从 1 到 2 ,只有第一栋建筑的平均高度为 2 / 1 = 2。
从 2 到 3 ,这三座建筑的平均高度均为 (2+3+3) / 3 = 2。
从 3 到 5 ,第二和第三栋楼都在那里,平均高度为 (3+3) / 2 = 3。
从 5 到 8 ,只有最后一栋建筑的平均高度为 3 / 1 = 3。
从 1 到 3 的平均高度是相同的,所以我们可以把它们分成一个部分。
从 3 到 8 的平均高度是相同的,所以我们可以把它们分成一个部分。

示例 3:

输入: buildings = [[1,2,1],[5,6,1]]
输出: [[1,2,1],[5,6,1]]
解释:
从 1 到 2 ,只有第一栋建筑的平均高度为 1 / 1 = 1。
从 2 到 5 ,没有建筑物,因此不包括在输出中。
从 5 到 6 ,只有第二栋建筑的平均高度为 1 / 1 = 1。
我们无法将这些部分组合在一起,因为没有建筑的空白空间将这些部分隔开。

提示:

  • 1 <= buildings.length <= 10^5
  • buildings[i].length == 3
  • 0 <= starti < endi <= 10^8
  • 1 <= heighti <= 10^5

提示 1

Try sorting the start and end points of each building.


提示 2

The naive solution is to go through each position on the street and keep track of the sum of all the buildings at that position and the number of buildings at that position.


提示 3

How could we optimize that solution to pass?


提示 4

We don't need to go through every position, just the ones where a building starts or a building ends.

解法1:事件处理 + 扫描线 + 差分 + 排序

题目分析

这个问题本质上是要我们处理一系列的建筑物,并将它们合并成尽可能少的区域,每个区域内所有位置的平均高度相同。

算法思路

  1. 理解问题:首先要理解题目中的“平均高度”定义,以及如何将建筑物合并成区域。
  2. 排序:根据建筑物的起始和结束位置对所有建筑物进行排序,因为合并操作依赖于建筑物的相对位置。
  3. 事件处理:将每个建筑物的开始和结束视为“事件”,并在排序后的序列中按顺序处理这些事件。
  4. 差分数组:使用差分数组来维护当前区域内建筑物的总高度和数量,以便于快速计算平均高度。
  5. 合并区域:当遇到建筑物结束或开始时,根据当前累计的高度和数量,决定是否需要合并或分割区域。

详细步骤

  1. 创建事件列表:遍历每个建筑物,将其起始位置和结束位置作为事件添加到事件列表中,同时记录建筑物的高度和数量变化(正负1)。

  2. 排序事件:按照事件的位置排序,如果位置相同,则按照高度排序,确保结束事件在开始事件之前。

  3. 初始化变量:初始化当前高度、当前数量、前一个事件位置,并准备一个列表来存储结果区域。

  4. 遍历事件:按顺序处理每个事件:

    • 如果当前数量大于0且当前位置与前一个事件位置不同,说明当前区域结束,需要计算平均高度并添加到结果列表。
    • 更新当前高度和数量,根据当前事件是建筑物的开始还是结束。
    • 更新前一个事件位置。
  5. 添加结果:在处理完所有事件后,如果最后一个区域未结束,需要将其添加到结果列表。

  6. 返回结果:返回存储所有区域的列表。

代码实现

Java版:

class Solution {
    public int[][] averageHeightOfBuildings(int[][] buildings) {
        List<int[]> events = new ArrayList<>();
        for (int[] build: buildings) {
            events.add(new int[]{build[0], build[2], 1});
            events.add(new int[]{build[1], -build[2], -1});
        }

        Collections.sort(events, new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return a[0] != b[0] ? a[0] - b[0] : a[1] - b[1];
            }
        });

        List<int[]> street = new ArrayList<>();
        int cnt = 0;
        int height = 0;
        int prev = 0;
        for (int[] event: events) {
            if (cnt > 0 && prev != event[0]) {
                if (street.size() == 0) {
                    street.add(new int[]{prev, event[0], height / cnt});
                } else {
                    int[] last = street.get(street.size() - 1);
                    if (last[1] == prev && last[2] == height / cnt) {
                        last[1] = event[0];
                        street.set(street.size() - 1, last);
                    } else {
                        street.add(new int[]{prev, event[0], height / cnt});
                    }
                }
            }
            cnt += event[2];
            height += event[1];
            prev = event[0];
        }
        return street.toArray(new int[street.size()][]);
    }
}

Python3版:

class Solution:
    def averageHeightOfBuildings(self, buildings: List[List[int]]) -> List[List[int]]:
        events = []
        for start, end, height in buildings:
            events.append([start, height, 1])
            events.append([end, -height, -1])
        
        events.sort()
        street = []
        cnt = 0
        height = 0
        prev = 0
        for pos, command, amount in events:
            if cnt > 0 and prev != pos:
                if len(street) == 0:
                    street.append([prev, pos, height // cnt])
                elif street[-1][1] == prev and street[-1][2] == height // cnt:
                    street[-1][1] = pos 
                else:
                    street.append([prev, pos, height // cnt])
            
            cnt += amount
            height += command
            prev = pos

        return street

复杂度分析

  • 时间复杂度:O(NlogN),其中 N 是数组 buildings 的长度,主要花费在排序上。。

  • 空间复杂度:O(N),用于存储事件和结果列表。

总结

这个问题通过将建筑物的开始和结束视为事件,然后按顺序处理这些事件,可以有效地找到最少数量的非重叠区域,每个区域内的平均高度相同。使用差分数组可以快速更新区域内的高度和数量,从而快速得到平均高度。

类似题型:LeetCode 759. 员工空闲时间-CSDN博客

LeetCode 2021. 街上最亮的位置-CSDN博客

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值