leetcode 218. The Skyline Problem java代码

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_MAX0 < 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], ...]


这两天在学习线段树和树状数组,就在了他从的上做了这道题,首先讲一下使用线段树解这道题的思路:

首先是线段树的节点的定义:每个节点的left和right定义了节点所代表的线段的范围,leftChild和rightChild分别是节点所代表的线段的的左右半边,需要特别注意的是isCover和high变量的含义,IsCover表示该节点代表的线段是否被更高的线段完全覆盖;high表示比该节点代表的线段高的线段中,最高的高度值,这些线段不是必须要完全覆盖节点代表的线段,只要在x轴的投影有重叠即可。这样定义节点的原因是首先可以节省空间,因为题设中x的取值范围特别大,如果建立完全的线段树,肯定是要超出内存限制的,使用了isCover之后,如果isCover是true,那么就不用向下扩展了,因为他的所有子节点都会被同一条线段覆盖;此外,可以提高速度,在建立了线段树之后,如果要查询一个单位长度的线段的高度,只需要查找到包括这个单位线段并且isCover值是true的节点即可。

class Node{
boolean isCover;//该节点表示的线段是否被更高的线段完全覆盖
int left;
int right;
int high;//该节点表示的线段上最高的高度,可以是部分覆盖
Node leftChild;
Node rightChild;
Node(int l,int r){
left =l;
right = r;
high =0;
isCover =false;
}
}

定义了节点之后最重要的就是创建线段树的算法了,节点和线段的可能的关系在代码的注释中都已经给出,就不再赘述。

  void insertSeg(int left,int right,Node tree,int high){
    //设此次判断的线段为a
    int mid = (tree.left+ tree.right)/2;
        if(left<= tree.left && right >= tree.right){//完全覆盖
        if(tree.high<=high){//
        tree.high = high;
        tree.isCover = true;
        return;
       
        //即使a的高度低于该节点,但是有可能高于该节点的子节点,因此还要继续判断
        }
        if(tree.isCover){//本节点已经被别的线段完全覆盖,设为线段b
        if(tree.high>high){//这个线段的高度低于b的高度(此时tree.high等于b的高度),因此忽略该线段
        return;
        }
        else{//这个线段的高度高于节点的高度,并且不能完全覆盖该线段,
        //因此该节点变为不能被更高的完全覆盖的状态,
        //但是需要注意的是,这个节点的子节点仍然有可能处于被线段b完全覆盖的状态,
                         //因此需要扩展本节点的子节点,并且将状态设置为完全覆盖,高度设置为b的高度(此时tree.high等于                          //b的高度)。
        tree.isCover = false;
        if(tree.rightChild == null){
            tree.rightChild = new Node(mid, tree.right);
            tree.rightChild.high = tree.high;
            tree.rightChild.isCover = true;
            }
        else{
        tree.rightChild.high = tree.high;
            tree.rightChild.isCover = true;
        }
            if(tree.leftChild == null){
            tree.leftChild = new Node(tree.left, mid);
            tree.leftChild.high = tree.high;
            tree.leftChild.isCover = true;
            }
            else{
            tree.leftChild.high = tree.high;
            tree.leftChild.isCover = true;
            }
            tree.high = high; //子节点设置完毕后,将该节点的高度设置为a的高度
        }
        }
        //保证在没有被完全覆盖情况下,tree,high表示当前线段上部分覆盖的线段的最大高度值
        if(tree.high<high){
    tree.high = high;
    }
        //之后就是将线段a依次作用于本节点的左右子节点,处理过程和本节点相同,因此使用递归
        if(left >= mid){
        if(tree.rightChild == null){
        tree.rightChild = new Node(mid, tree.right);
        }
            insertSeg(left,right,tree.rightChild,high);
        }
        else if(right <= mid){
        if(tree.leftChild == null){
        tree.leftChild = new Node(tree.left, mid);
        }
            insertSeg(left,right,tree.leftChild,high);
        }
        else{
        if(tree.rightChild == null){
        tree.rightChild = new Node(mid, tree.right);
        }
        if(tree.leftChild == null){
        tree.leftChild = new Node(tree.left, mid);
        }
            insertSeg(left,right,tree.rightChild,high);
            insertSeg(left,right,tree.leftChild,high);
        }
    }

之后就是建立线段树和求解了,这里还有两个问题需要注意,如果题目中给出的矩形特别稀疏的话,会造成线段树过大,造成超出内存限制,因此使用分段计算方法;此外因为题目中要求输出要按照x坐标排序,并且所有可能的x坐标都是输入矩形的顶点的x坐标,因此使用最小堆对所有顶点的x坐标排序,然后顺序遍历这下x坐标求出结果。这两个方法在代码中都有注释。

最后是完整代码:

public class Solution {

	int[] heap;
	int heapIndex;
	Node tree;
	class Node{
		boolean isCover;//该节点表示的线段是否被完全覆盖
		int left;
		int right;
		int high;//该节点表示的线段上最高的高度,可以是部分覆盖
		Node leftChild;
		Node rightChild;
		Node(int l,int r){
			left =l;
			right = r;
			high =0;
			isCover =false;
		}
	}
	
	public List<int[]> getSkyline(int[][] buildings) {
		List<int[]> result = new ArrayList<int[]>();
		if(buildings.length == 0)
			return result;
		int i,k,j;
		        
        //分段计算所求节点,因为中间如果有较大的空格的话会造成线段树过大,造成超出内存限制
        int right = buildings[0][1],preNode = 0;
		List<int[]> subResult;
		int[][] sub;
        for(i =1;i<buildings.length;i++){
        	if(buildings[i][0]>right){//说明从i-1和i建筑之间不连接
        		//调用方法
        		sub = new int[i-preNode][3];
        		for(j=0;j<i-preNode;j++){
        			for(k=0;k<3;k++){
        				sub[j][k] = buildings[preNode+j][k];
        			}
        		}
        		
        		subResult = getSubSkyline(sub);
        		result.addAll(subResult);
        		
        		//开始找下一段
        		right = buildings[i][1];
        		preNode = i;
        	}
        	else{
        		if(buildings[i][1]>right){
        			right = buildings[i][1];
        		}
        	}
        }
        //处理最后一段
        sub = new int[i-preNode][3];
		for(j=0;j<i-preNode;j++){
			for(k=0;k<3;k++){
				sub[j][k] = buildings[preNode+j][k];
			}
		}
		
		subResult = getSubSkyline(sub);
		result.addAll(subResult);
		
		return result;
	}
	
	
	public List<int[]> getSubSkyline(int[][] buildings) {
		List<int[]> result = new ArrayList<int[]>();
		
        int right,left,i,maxRight;
               
        maxRight = 0;
        for(i = 0;i<buildings.length;i++){
            if(buildings[i][1] > maxRight){
                maxRight = buildings[i][1];
            }
        }
        
        left = buildings[0][0];
        right = maxRight;
        
        //初始化根节点
        tree = new Node(left,right);       
        //初始化树
        for(i = 0;i<buildings.length;i++){
            insertSeg(buildings[i][0],buildings[i][1],tree,buildings[i][2]);
        }
        
        //初始化最小堆
      	heap = new int[2*buildings.length+1];
        for(i=1;i<=buildings.length;i++){
           heap[i] = buildings[i-1][0];
        }
        heapIndex = i;
        for(i=0;i<buildings.length;i++){
           insertHeap(buildings[i][1]);
        }
        
        int nowHigh = 0;
        int x,r;
        int[] v; 
        for(i=0;i<2*buildings.length-1;i++){
        	x = getMin();
        	r = getHigh(x,tree);
            if(r!=nowHigh){
                nowHigh = r;
                v = new int[2];
                v[0] = x;
                v[1] = r;
                result.add(v);
            }
        }
        v = new int[2];
        v[0] = maxRight;
        v[1] = 0;
        result.add(v);
        return result;
    }
	
	int getMin(){
		int result = heap[1];
		heap[1] = heap[--heapIndex];
		changeFromHead();
		return result;		
	}
	
	void changeFromHead(){
		int index = 1,l,r,c,v;
		while(true){
			l = index*2;
			r = l+1;
			
			if(l>= heapIndex)
				break;
			
			if(l == heapIndex - 1){
				c = l;
			}
			else{
				if(heap[l]>heap[r]){
				c = r;
			}
			else{
				c = l;
			}
		}
			if(heap[index]>heap[c]){
				v = heap[index];
				heap[index] = heap[c];
				heap[c] = v;
				index = c;
			}
			else{
				break;
			}
		}
	}
	
	void insertHeap(int value){
		heap[heapIndex++] = value;
		changeHeap(heapIndex-1);
	}
	
	void changeHeap(int index){
		int father,c;
		while(index>1){
			father = index/2;
			if(heap[father]> heap[index]){
				c = heap[father];
				heap[father] = heap[index];
				heap[index] = c;
				index = father;
			}
			else{
				break;
			}
		}
	}
    
    int getHigh(int left,Node tree){
        int result;
        int mid;
        while(true){
        	if(tree == null){
        		result = 0;
                return result;
        	}
        	if(tree.isCover){
        		break;
        	}
        	mid = (tree.left+ tree.right)/2;
            if(left >= mid){
                tree = tree.rightChild;
            }
            else{
            	tree = tree.leftChild;
            }
        }
        result = tree.high;
        return result;
    }
    
    void insertSeg(int left,int right,Node tree,int high){
    	//设此次判断的线段为a
    	int mid = (tree.left+ tree.right)/2;
        if(left<= tree.left && right >= tree.right){//完全覆盖
        	if(tree.high<=high){//
        		tree.high = high;
        		tree.isCover = true;
        		return;
        	} 
        	//即使a的高度低于该节点,但是有可能高于该节点的子节点,因此还要继续判断
        }
        if(tree.isCover){//本节点已经被别的线段完全覆盖,设为线段b
        	if(tree.high>high){//这个线段的高度低于b的高度(此时tree.high等于b的高度),因此忽略该线段
        		return;
        	}
        	else{//这个线段的高度高于节点的高度,并且不能完全覆盖该线段,
        		 //因此该节点变为不能被更高的完全覆盖的状态,
        		 //但是需要注意的是,这个节点的子节点仍然有可能处于被线段b完全覆盖的状态,
        		 //因此需要扩展本节点的子节点,并且将状态设置为完全覆盖,高度设置为b的高度(此时tree.high等于b的高度)。
        		tree.isCover = false;
        		if(tree.rightChild == null){
        			System.out.println("create node " + left);
            		tree.rightChild = new Node(mid, tree.right);
            		tree.rightChild.high = tree.high;
            		tree.rightChild.isCover = true;
            	}
        		else{
        			tree.rightChild.high = tree.high;
            		tree.rightChild.isCover = true;
        		}
            	if(tree.leftChild == null){
            		System.out.println("create node " + left);
            		tree.leftChild = new Node(tree.left, mid);
            		tree.leftChild.high = tree.high;
            		tree.leftChild.isCover = true;
            	}
            	else{
            		tree.leftChild.high = tree.high;
            		tree.leftChild.isCover = true;
            	}
            	tree.high = high; //子节点设置完毕后,将该节点的高度设置为a的高度
        	}
        }
        //保证在没有被完全覆盖情况下,tree,high表示当前线段上部分覆盖的线段的最大高度值
        if(tree.high<high){
    		tree.high = high;
    	}
        //之后就是将线段a依次作用于本节点的左右子节点,处理过程和本节点相同,因此使用递归
        if(left >= mid){
        	if(tree.rightChild == null){
        		System.out.println("create node " + left);
        		tree.rightChild = new Node(mid, tree.right);
        	}
            insertSeg(left,right,tree.rightChild,high);
        }
        else if(right <= mid){
        	if(tree.leftChild == null){
        		System.out.println("create node " + left);
        		tree.leftChild = new Node(tree.left, mid);
        	}
            insertSeg(left,right,tree.leftChild,high);
        }
        else{
        	if(tree.rightChild == null){
        		System.out.println("create node " + left);
        		tree.rightChild = new Node(mid, tree.right);
        	}
        	if(tree.leftChild == null){
        		System.out.println("create node " + left);
        		tree.leftChild = new Node(tree.left, mid);
        	}
            insertSeg(left,right,tree.rightChild,high);
            insertSeg(left,right,tree.leftChild,high);
        }
    }

}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值