leetcode 218. 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).

Buildings Skyline Contour
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], …]

给出大楼的(start x, end x, height)坐标,让output出外轮廓的关键点,一般是左上角(x,height), 最后一个点是右下角的(x,0)

总结一个很易懂的方法:

在这里插入图片描述

图中在x=2时,最大height由0变为10,所以output一个(2,10)
x=3时,最大height由10变为15,所以output(3,15)
之后在x=7时,最大height由15变为12,所以output(7,12)

以此类推,最大的height发生变化时,添加(x,最大height)到output

再看一个例子,把上图稍做更改
注意圈出来的地方,同一个x有两个不同的height
注意input是(左x,右x,height),把它拆成(左x,height),(右x,height)

(1) start点(左x,height)case:
比如x=2时,height=10和height=15
这时如果先读入height=10,那么height由0变为10,会错误地输出(2,10)
而如果先读入较大的height,height由0变为15,输出(2,15),再读入height=10时,最大的height保持15不变,就不会输出(2,10)
**总结:在起始点(左x,height)时,要先读入较大的height

(2)end点(右x,height)case:
比如x=24时,如果先读入较大的height=10,那么height=10移出现有的height列表中,剩下的最大height是黄色大楼的height=8,height由10变为8,会错误地输出(24,8)
而如果先读入较小的height,则先移除8,剩下的最大height仍然是10,所以不输出,而后移除10时,最大height由10变为0,所以正确地输出(24,0)
**总结:在end点(右x,height)时,要先读入较小的height

(3)start点和end点case:
显然要start和end出现时,先读入start
在这里插入图片描述

根据以上分析,定义一个BuildNode类,它应该包含x,height和是否是start的flag信息
为了确保以下3点,BuildNode需要具备自定义排序的功能
(1)start点时,先读入较大的height
(2)end点时,先读入较小的height
(3)start点先于end点读入

这个BuildNode类应该支持排序功能,所以让它implements Comparable类

class BuildNode implements Comparable{
    int x = 0;
    int height = 0;
    boolean isStart = true;
    
    public BuildNode(int x, int height, boolean isStart) {
        this.x = x;
        this.height = height;
        this.isStart = isStart;
    }
    
    public int compareTo(Object o) {
        if (this.x == ((BuildNode) o).x) {
            //if x is same, then if start node, larger height first
            //if end node, lower height first
            //if start and end node, start node first
            return (this.isStart ? -this.height: this.height) - 
                (((BuildNode) o).isStart ? 
                 -((BuildNode) o).height : ((BuildNode) o).height);
        } else {
            //left x first..
            return this.x - ((BuildNode) o).x;
        }
    }
}

BuildNode定义好了,接下来要把input的(左x,右x,height)
分解为(左x,height,start)和(右x,height,end)

所以要定义一个BuildNode数组,长度是输入数组的2倍
读入input,并要按照上述3点进行排序

		BuildNode[] nodes = new BuildNode[buildings.length * 2];
        
        for(int i = 0; i < buildings.length; i++) {
            //start point, height, start flag(true)..
            nodes[2 * i] = new BuildNode(buildings[i][0], buildings[i][2], true);
            //end point, height, start flag(false)..
            nodes[2 * i + 1] = new BuildNode(buildings[i][1], buildings[i][2], false);
        }
        Arrays.sort(nodes);

接下来要对input的BuildNode数组遍历,进行处理,得到output:

如何在每次读入start点和end点之后取得最大height,这时候需要一个优先队列来保存
而且同一height可能多次出现,所以要保存count
这里选用TreeMap结构,因为它用红黑树实现,在remove元素的时候是O(logn)的复杂度

每读入一个start点
在TreeMap中找到对应的height,把count+1,如果没有,就把count设为1。

每读入一个end点
在TreeMap中找到对应的height,把count-1(因为每次是先读入start点,所以不会出现不存在的情况),如果count-1后为0,说明这个height已经不存在了,从TreeMap中remove掉。

input中没有height=0,而output中有height=0
所以先在TreeMap中add(0,1)

        //key: height, value:count..
        //priority queue..
        TreeMap<Integer, Integer> height = new TreeMap<Integer,Integer>();
        height.put(0, 1);  //baseline..

然后就按顺序读入BuildNode数组的元素,最大height发生变化时output(x,最大height)

        for(int i = 0; i < nodes.length; i++) {
            //if start node, find the height and count+1..
            if (nodes[i].isStart) {
                height.compute(nodes[i].height, (key, value) -> {
                  if (value == null) {
                        return 1;
                  } else {
                       return value + 1;
                  }
                });
            //if end node, find the height and count-1, if count-1==0, then remove..
            } else {
                height.compute(nodes[i].height, (key, value) -> {
                    if (value == 1) {
                        return null; //if return null, treeMap will remove the node
                    }
                    return value - 1;
                });
            }
            
            curMaxHeight = height.lastKey(); //the largest key
            //if max height changes, save the node(x, height)..
            //update preMaxHeight..
            if (curMaxHeight != preMaxHeight) {
                ArrayList<Integer> list = new ArrayList<>();
                list.add(nodes[i].x);
                list.add(curMaxHeight);
                result.add(list);
                preMaxHeight = curMaxHeight;
            }            
        }

完整代码:

    public List<List<Integer>> getSkyline(int[][] buildings) {
        int preMaxHeight = 0;
        int curMaxHeight = 0;
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        
        if (buildings == null || buildings.length == 0) {
            return result;
        }
        
        //key: height, value:count..
        //priority queue, can sort..
        TreeMap<Integer, Integer> height = new TreeMap<Integer,Integer>();
        //save start and end point..
        BuildNode[] nodes = new BuildNode[buildings.length * 2];
        
        height.put(0, 1);  //baseline..
        
        for(int i = 0; i < buildings.length; i++) {
            //start point, height, start flag(true)..
            nodes[2 * i] = new BuildNode(buildings[i][0], buildings[i][2], true);
            //end point, height, start flag(false)..
            nodes[2 * i + 1] = new BuildNode(buildings[i][1], buildings[i][2], false);
        }
        
        Arrays.sort(nodes);
        
        for(int i = 0; i < nodes.length; i++) {
            //if start node, find the height and count+1..
            if (nodes[i].isStart) {
                height.compute(nodes[i].height, (key, value) -> {
                  if (value == null) {
                        return 1;
                  } else {
                       return value + 1;
                  }
                });
            //if end node, find the height and count-1, if count-1==0, then remove..
            } else {
                height.compute(nodes[i].height, (key, value) -> {
                    if (value == 1) {
                        return null; //if return null, treeMap will remove the node
                    }
                    return value - 1;
                });
            }
            
            curMaxHeight = height.lastKey(); //the largest key
            //if max height changes, save the node(x, height)..
            //update preMaxHeight..
            if (curMaxHeight != preMaxHeight) {
                ArrayList<Integer> list = new ArrayList<>();
                list.add(nodes[i].x);
                list.add(curMaxHeight);
                result.add(list);
                preMaxHeight = curMaxHeight;
            }            
        }
        return result;  
    }


class BuildNode implements Comparable{
    int x = 0;
    int height = 0;
    boolean isStart = true;
    
    public BuildNode(int x, int height, boolean isStart) {
        this.x = x;
        this.height = height;
        this.isStart = isStart;
    }
    
    public int compareTo(Object o) {
        if (this.x == ((BuildNode) o).x) {
            //if x is same, then if start node, larger height first
            //if end node, lower height first
            //if start and end node, start node first
            return (this.isStart ? -this.height: this.height) - 
                (((BuildNode) o).isStart ? 
                 -((BuildNode) o).height : ((BuildNode) o).height);
        } else {
            //left x first..
            return this.x - ((BuildNode) o).x;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值