leetcode218. 天际线问题 线段树+离散化

城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。现在,假设您获得了城市风光照片(图A)上显示的所有建筑物的位置和高度,请编写一个程序以输出由这些建筑物形成的天际线(图B)。

每个建筑物的几何信息用三元组 [Li,Ri,Hi] 表示,其中 Li 和 Ri 分别是第 i 座建筑物左右边缘的 x 坐标,Hi 是其高度。可以保证 0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX 和 Ri - Li > 0。您可以假设所有建筑物都是在绝对平坦且高度为 0 的表面上的完美矩形。
例如,图A中所有建筑物的尺寸记录为:[ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] 。
输出是以 [ [x1,y1], [x2, y2], [x3, y3], … ] 格式的“关键点”(图B中的红点)的列表,它们唯一地定义了天际线。关键点是水平线段的左端点。请注意,最右侧建筑物的最后一个关键点仅用于标记天际线的终点,并始终为零高度。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。
例如,图B中的天际线应该表示为:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ]。
说明:
任何输入列表中的建筑物数量保证在 [0, 10000] 范围内。
输入列表已经按升序排列在左边的 x 位置 Li 。
输出列表必须按 x 位排序。
输出天际线中不得有连续的相同高度的水平线。例如 […[2 3], [4 5], [7 5], [11 5], [12 7]…] 是不正确的答案;三条高度为 5 的线应该在最终输出中合并为一个:[…[2 3], [4 5], [12 7], …]

思路:这道题目的目的是为了让我们输出每个房子右边的点。
那我们用线段树应该怎么做呢。
首先由于坐标问题,我们应当先选择离散化一下,基本很多线段树的题都需要离散化作为辅助。
离散化好了,我们应该选择怎么构建线段树,我们需要维护每个点的最大值(不需要维护区间的最大值)。
我们每次插入一个矩形,先知道该矩形的左右坐标,然后更新这段l~r的区间(维护该区间每个点的最大性)。
但是由于矩形相交,那么我们取节点的话会取这一部分的第二高的矩形的交点(红蓝交界处),(如果维护第二高)这样子维护的
话很麻烦,但是我们这里可以取巧,即红色矩形我们更新[l~r-1]部分即可,那么r结点的值即为绿色的高(当前最大值)。
(因为这题不涉及区间求值,需要确切到具体的某个坐标点,所以这题不需要
pushUp,不过我们需要pushDown一下,即懒惰标记一下,可以减少递归深度,我们将矩形的高作为懒惰标记).
class Solution {
    public static int[] x;
    public static int[] max;
    public static int lower_bound(int[] array,int l,int r,int key){ //离散化用到
        while (lint mid=(l+r)>>1;
            if(array[mid]>=key){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;
    }
    public static void pushDown(int rt){
        if(max[rt]>0){ //如果该节点h不为0 则向下更新
            max[rt<<1]=Math.max(max[rt],max[rt<<1]); //更新最大
            max[rt<<1|1]=Math.max(max[rt],max[rt<<1|1]); //更新最大
            max[rt]=0; //变成0
        }
    }
    public void update(int L,int R,int h,int l,int r,int rt){
        if(max[rt]>=h){ //不需要再向下更新了
            return;
        }
        if(L<=l&&r<=R){  //更新该区间
            max[rt]=h;
            return;
        }
        pushDown(rt); //懒惰标记
        int mid=(l+r)>>1;
        if(L<=mid){
            update(L,R,h,l,mid,rt<<1);
        }
        if(R>mid){
            update(L,R,h,mid+1,r,rt<<1|1);
        }
    }
    public void query(List<int[]> ans,int l,int r,int rt){
        if(l==r){
            if(!ans.isEmpty()&&max[rt]==
                    ans.get(ans.size()-1)[1]){ //同区间高度一样取第一个 (2,10)(5,10)(7,10)取第一个
                return;
            }
            ans.add(new int[]{x[l],max[rt]});//该区间l的左坐标为x[l] 高度为max[rt]
            //平地部分高为0 且左边坐标为距离最近矩形的右边左边
            return;
        }
        //遍历整个区间
        pushDown(rt);
        int mid=(l+r)>>1;
        query(ans,l,mid,rt<<1);
        query(ans,mid+1,r,rt<<1|1);
    }
    public List<int[]> getSkyline(int[][] buildings) {
        if(buildings.length==0||buildings[0].length==0){
            return new ArrayList<>();
        }
        int ct=0;
        int n=buildings.length;
        x=new int[n<<1];
        List<int[]> ans=new ArrayList<>();
        for(int i=0;i//先取各个坐标节点
            x[ct++]=buildings[i][0];
            x[ct++]=buildings[i][1];
        }
        SortedSet set=new TreeSet(); //离散化
        for(int t:x){
            set.add(t);
        }
        int k=0;
        for(int t:set){
            x[k++]=t;
        }
        max=new int[k<<2]; //建树 初始化为0
        for(int i=0;iint l=lower_bound(x,0,k-1,buildings[i][0]); //取第一个矩形左右坐标的位置
            int r=lower_bound(x,0,k-1,buildings[i][1])-1; //r-1取巧
            update(l,r,buildings[i][2],0,k-1,1); //更新
        }
        query(ans,0,k-1,1);//查询整个区间,即(0~k-1)部分.
        return ans;
    }
}

你可能感兴趣的:(线段树,线段树,离散化)