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)

总结一个很易懂的方法:

leetcode 218. The Skyline Problem (介绍一种易懂的方法)_第1张图片

图中在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
leetcode 218. The Skyline Problem (介绍一种易懂的方法)_第2张图片

根据以上分析,定义一个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 height = new TreeMap();
        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 list = new ArrayList<>();
                list.add(nodes[i].x);
                list.add(curMaxHeight);
                result.add(list);
                preMaxHeight = curMaxHeight;
            }            
        }

完整代码:

    public List> getSkyline(int[][] buildings) {
        int preMaxHeight = 0;
        int curMaxHeight = 0;
        List> result = new ArrayList>();
        
        if (buildings == null || buildings.length == 0) {
            return result;
        }
        
        //key: height, value:count..
        //priority queue, can sort..
        TreeMap height = new TreeMap();
        //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 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;
        }
    }
}

你可能感兴趣的:(leetcode,算法)