A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).
The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi]
, where Li
and Ri
are the x coordinates of the left and right edge of the ith building, respectively, and Hi
is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX
, 0 < Hi ≤ INT_MAX
, and Ri - Li > 0
. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.
For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ]
.
The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ]
that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.
For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]
.
Notes:
- The number of buildings in any input list is guaranteed to be in the range
[0, 10000]
. - The input list is already sorted in ascending order by the left x position
Li
. - The output list must be sorted by the x position.
- There must be no consecutive horizontal lines of equal height in the output skyline. For instance,
[...[2 3], [4 5], [7 5], [11 5], [12 7]...]
is not acceptable; the three lines of height 5 should be merged into one in the final output as such:[...[2 3], [4 5], [12 7], ...]
天际线问题。题意不难理解,我这里提及一些重点。首先,天际线都是一些横的线段,你会发现最后要求的输出不是线段,而是一些点的坐标。这些点横着看,都是一些线段的起点;同时这些点一定是在高度有变化的坐标发生的,但是也分情况,如果高度变大,那么输出的点是高度更大的点;如果高度是变小的,那么输出的点是高度更小的。
思路是扫描线 + 最大堆。因为input buildings的形式是[left, right, height],表示的是每一个房子的左边缘,右边缘和高度。我们首先将input buildings放入一个list buildLines,做一个转换,把每个房子的左边缘和右边缘的高度拆分出来,这样我在buildLines里面存的都是一些边缘和他们各自的高度。但是为了区分左边缘和右边缘,我把左边缘的高度暂时标记成负数。最后我再把buildLines按照高度做一个排序,这样左边缘会相对靠前(都是负数);如果边缘下标一样,高度较小的靠前。注意这个地方很巧妙地用负数区分了左边缘的高度和右边缘的高度,在排序的时候也确保在遍历左边缘的时候,会先处理高度更高的左边缘,而遇到右边缘的时候,会先处理高度更低的。同时我们需要一个最大堆maxHeap和一个变量preHighest记录之前最高的高度是多少。
再次遍历buildLines,当遇到一个左边缘的时候(< 0),我们把他放到最大堆,但如果是一个右边缘,我们直接从最大堆抛弃这个边。此时如果堆顶元素curHeight跟之前一个最大高度preHighest不同的话,堆顶元素curHeight就是一条天际线的起点,他的下标就是当前遍历到的左边缘的下标。把这个结果加入结果集之后,记得更新preHighest。
时间O(n^2)
空间O(n)
Java实现
1 class Solution { 2 public List<List<Integer>> getSkyline(int[][] buildings) { 3 List<List<Integer>> res = new ArrayList<>(); 4 List<int[]> buildLines = new ArrayList<>(); 5 for (int[] points : buildings) { 6 // build[0, 1, 2] 7 // build[left, right, height] 8 buildLines.add(new int[] { points[0], -points[2] }); 9 buildLines.add(new int[] { points[1], points[2] }); 10 } 11 // 从左往右再从小到大排序 12 // buildLines[0, 1] 13 // buildLines[bound, height] 14 Collections.sort(buildLines, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]); 15 PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a); 16 maxHeap.add(0); 17 int preHighest = 0; 18 for (int[] points : buildLines) { 19 if (points[1] < 0) { 20 maxHeap.add(-points[1]); 21 } else { 22 maxHeap.remove(points[1]); 23 } 24 int curHeight = maxHeap.peek(); 25 if (curHeight != preHighest) { 26 res.add(Arrays.asList(points[0], curHeight)); 27 preHighest = curHeight; 28 } 29 } 30 return res; 31 } 32 }