城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回由这些建筑物形成的 天际线 。
每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:
- lefti 是第 i 座建筑物左边缘的 x 坐标。
- righti 是第 i 座建筑物右边缘的 x 坐标。
- heighti 是第 i 座建筑物的高度。
天际线 应该表示为由 “关键点” 组成的列表,格式 [[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]]
解题思路
通过观察我们可以得出
- 关键点总是来源于建筑物的左边缘或者右边缘的
- 相邻两个关键点之间必定存在高度差
因此,我们可以只对所有建筑物的左右边缘进行遍历,关键点只在这些点当中产生
因为最终结果的关键点需要按照x坐标进行排序,因此我们先对所有建筑物的左右端点按x坐标进行一次统一的从小到大的排序,x坐标相同的话,按高度从小到大的排序
为了区分建筑物的左右端点,我们可以用负数表示左端点的高度,正数表示右端点的高度
假设遍历到某个端点cur,大顶堆中存放的是:该端点处于的x坐标,被若干个建筑物
例如,x=7时,存储的就是3个建筑物的高度,因为当前遍历的是红色建筑物的右端点,因此在当前x=7的坐标下,已经不被覆盖了,所以是2个建筑物的高度,而这两个建筑物的最大高度是12,和之前关键点的最大高度不一致,因此可以判断出现了关键点
总结
简单来说,在遍历端点的过程中,通过高度的正负来实现对建筑物左右端点的判断,遍历到左端点则进入优先队列,说明后面的若干个x坐标都被当前高度为h的建筑物覆盖,遍历到右端点则退出优先队列,说明后面的所有x坐标都不会被当前高度为h的建筑物覆盖。所以通过优先队列,我们就可以知道,当前的端点的x坐标覆盖了多少个建筑物,因为最高的建筑物会遮盖其他低的建筑物,所以我们维护的是一个大顶堆。
又因为关键点和前一个关键点的高度必然是不一样的,所以只要当前的堆顶元素和前一个关键点的高度不一样,我们就可以判断当前x坐标也是一个关键点
代码
class Solution {public List<List<Integer>> getSkyline(int[][] buildings) {List<List<Integer>> res=new ArrayList<>();List<int[]> cnt=new ArrayList<>();for (int[] building : buildings) {cnt.add(new int[]{building[0],-building[2]});cnt.add(new int[]{building[1],building[2]});}int i=0,pre=0;Collections.sort(cnt,(o1, o2) -> o1[0]==o2[0]?o1[1]-o2[1]:o1[0]-o2[0]);PriorityQueue<Integer>priorityQueue=new PriorityQueue<>((o1, o2) -> o2-o1);priorityQueue.add(0);for (int[] cur : cnt) {if(cur[1]<0){priorityQueue.add(-cur[1]);}else{priorityQueue.remove(cur[1]);}int h=priorityQueue.peek();if(h!=pre){res.add(Arrays.asList(cur[0],h));pre=h;}}return res;}
}