- 首先要画出轮廓线,其实就是画轮廓点,而轮廓点显示就是在建筑的左上右上顶点中,问题转化为找符合条件的顶点。
- 每个没被遮挡的高建筑左顶点一定是轮廓点;高建筑和矮建筑交汇的地方,取的是高建筑右顶点的横坐标和矮建筑的高度作为轮廓点;每一片连在一起的建筑的最右边的轮廓点,纵坐标取0
这题网上最主流的解法是用大顶堆来解,看了很久才理解,这里尝试解释一下:
- 将每条建筑的横线段分解成左上右上两个顶点,将所有这些点按横坐标大小升序排列
- 从左至右遍历这些点,每遍历到一个左顶点,将此点代表的建筑高度放入大顶堆height中
- 每次到一个左顶点,先比较此顶点高度与当前基准高度,如果高于基准高度,那么就是一个轮廓点。这是最关键的地方,结合图形理解,如果当前建筑的左顶点要作比较,肯定是与它前面有重叠的建筑比较,而前面重叠的建筑高度,要取之前最高的、横线还在延续的建筑比较,因此需要用到一个大顶堆维护当前高度
还有两个细节:
1. 将右顶点的高度设为负值,在遍历点时用以区分左右顶点
2. 碰到左顶点将高度加入大顶堆,碰到右顶点时,说明此建筑横向的延绵结束了,那么要从大顶堆中删掉此高度
public List<int[]> getSkyline(int[][] buildings) {
List<int[]> list = new ArrayList<int[]>();
Set<int []>set = new TreeSet<int[]>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {//每个顶点先横坐标排序
if(o1[0] == o2[0]){//横坐标相同时,纵坐标要降序排列
return o2[1] - o1[1];
}
return o1[0] - o2[0];
}
});
//高度降序优先队列(大顶堆)
PriorityQueue<Integer> height = new PriorityQueue<Integer>(11,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
//构造顶点横坐标升序set
for(int i=0;i<buildings.length;i++){
int[] A = {buildings[i][0],buildings[i][2]};
set.add(A);
//右顶点y值设为负值,便于区分
int[] B = {buildings[i][1],-1*buildings[i][2]};
set.add(B);
}
//遍历set
height.offer(0);
int preHeight = 0;
for(int[] A : set){
if(A[1] > 0){//左顶点
if(A[1] > preHeight){//左顶点大于上一高度
list.add(A);
}
height.offer(A[1]);//加入当前线段高度
preHeight = height.peek();//拿顶部元素作为上次高度
}
else{
height.remove(-1*A[1]);//右顶点时去掉大顶堆对应左顶点
preHeight = height.peek();//拿顶部元素作为上次高度
if(-1*A[1] > preHeight){//右顶点大于上一高度
int []temp = {A[0],preHeight};
list.add(temp);
}
}
}
return list;
}