一、题目描述(leetcode平台天际线问题:https://leetcode.com/problems/the-skyline-problem/)
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], ...]
二、解题思路
最开始看到题目,我大致脑补了一下解决过程,发现感觉并不是太复杂,后来才发现是我太天真!没有get到其中的玄机……不过后来在我扩充了对一些原来很少使用的数据结构的了解之后,最后的代码实现也并不是十分困难。
最开始我拿到题目,想到的就是使用扫描线的思想,以横轴为基准逐段扫描整个坐标轴,直至结束。在扫描的过程中对每一个节点的高度值进行确认,这样只需要扫描一次就能够完成整个功能的实现。
后来我发现,我最开始想的边读取边扫描的方式是很难达到目的的。在读取过程中,由于无法预判后面读取的数据是否会落在我目前扫描的这一段线段之中,会使整个工作变的较为复杂。我也想过利用int的离散性逐点去进行确定,但当数据量较大时,这些方式的时间复杂度都过大。
在遇到一些障碍之后,我决定从人类的自然方式去画线时实际用到的判断来找算法的思路。当我们自己画线时,我们遇到高度变化时(上升或下降),所画天际线的高度也会随之变化,而我们在画线时,永远只盯着最高的那一条水平线,而没有管在其之下有多少条水平线。而在高度需要下降时,我们将会将高度下降至该点下方目前最高的一条线。所以问题的关键就在于,我们如何获取下方最高的一条线的高度。显然,我们需要一种容器,存储多个水平线条的高度(也就是覆盖正被扫描坐标的各个线段高度),同时,我们要能够从该容器之中获取其中的最大值。显然,我们可以使用动态数组,或向量,再自己排序来实现这个功能,但为了简化代码,我采用了multiset多重集来实现,因为由STL库的说明,该类能够自动依据给定的key将其排序,这将省去我们的很多工作。同样的,我们在从左至右扫描时,也需要知道各个点的横坐标的排序,因此我也选用了multimap类来帮我实现这个功能。
由此我得到了一下算法的思路:
1. 读取所有的线段信息,将其转化为所有的左右端点的信息,并将所有端点的横纵坐标存储在一个multimap中,我们令一条水平线段的右端点在multimap中存储的高度为其本身高度的相反数,这样我们就能通过高度正负来判断这是一条线段的首节点还是尾节点。
2. 此时我们已经获得了所有的端点的信息,并得到了他们的升序排列。于是我们从横坐标最小的点开始,遇到一个点,若其高度为整数,将其高度添加到一个multiset集中,这个集中存储的就是目前正被扫描的线段的高度。若其高度为负数,我们就在集合中找到高度与其绝对值相等的元素,并将其删除。这样,我们可以保证multiset中永远只存储有被扫描中的线段的高度。
3. 下面是如何在扫描时对节点高度进行修改。
由于题目要求,若同一横坐标有多个点,则只取其中最高的那个点,若最高的点高度与前一个点高度相等,则它应该被删除。
由此,我创建了一个pair<int,int>类变量t_x来存储被扫描过的点的坐标。如果遇到的下一个点与t_x横坐标相等,则将t_x纵坐标修改为其中较大的值。如果遇到的下一个点与t_x横坐标不等,则说明t_x应该被压入results(存储最后返回的结果的向量)之中,此时我们需要判断,t_x的高度是否与results中存储的最后一个节点的纵坐标相等,若相等,则说明这依旧是一条水平的线条,那么t_x不应被压入results中。反之,压入。之后将t_x更新为新的横纵坐标。横坐标为下一个点的横坐标,而纵坐标则为multiset中当前的最大值。重复这样的操作直至结束,再对开头结束的一些特殊情况进行特判,就将得到我们需要的结果。将results向量返回即可。
三、代码实现
具体代码如图,已经经平台测试提交通过,由于代码注释写的较多故在此就不对代码细节再进行过多阐述。
四、总结
本次的小练习还是让我略略的了解了一些高级数据结构的使用的,例如set和map等,对我的编程水平还是有一定程度的提高。另外我也很好奇为什么java和python写出来的程序比c++要快不少。最后,关于世辰巨巨说的题目太简单的问题,小白表示,这个难度就已经够我们喝一壶了ORZ(此处为破涕为笑表情)