218. The Skyline Problem

60 篇文章 1 订阅
6 篇文章 0 订阅

问题

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).
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], …]
在这里插入图片描述

科普:The skyline problem:
https://briangordon.github.io/2014/08/the-skyline-problem.html

解法1 分治

    //5ms
    public List<int[]> getSkyline(int[][] buildings) {
        if (buildings == null || buildings.length == 0) {
            return new ArrayList<int[]>();
        }
        return merge(buildings, 0, buildings.length - 1);
    }
    
    private LinkedList<int[]> merge(int[][] buildings, int lo, int hi) {
        LinkedList<int[]> result = new LinkedList<>();
        if (lo > hi) return result;
        
        if (lo == hi) {
            int[] topLeft = new int[] { buildings[lo][0], buildings[lo][2] };
            int[] bottomRight = new int[] { buildings[lo][1], 0 };
            result.add(topLeft);
            result.add(bottomRight);
            return result;
        }
        
        int mid = lo + (hi - lo)/2;
        LinkedList<int[]> left = merge(buildings, lo, mid);
        LinkedList<int[]> right = merge(buildings, mid + 1, hi);
        int leftY = 0;
        int rightY = 0;
        
        while (!left.isEmpty() && !right.isEmpty()) {
            int x1 = left.peek()[0];
            int x2 = right.peek()[0];
            if (x1 < x2) {
                leftY = left.poll()[1];
            } else if (x2 < x1) {
                rightY = right.poll()[1];
            } else {
                leftY = left.poll()[1];
                rightY = right.poll()[1];
            }
            int y = Math.max(leftY, rightY);
            if (result.isEmpty() || result.peekLast()[1] != y) {
                result.add(new int[]{Math.min(x1, x2), y});
            }
        }
        
        result.addAll(left);
        result.addAll(right);

        return result;
        
    }

解法2 优先堆

    //2ms
    public List<int[]> getSkyline(int[][] buildings) {
        List<int[]> result = new ArrayList<>();
        PriorityQueue<int[]> bases = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[2] == b[2] ? a[1] - b[1] : b[2] - a[2]; //sort by y, then x2
            }
        });
        int[] pre = new int[]{-1, Integer.MAX_VALUE, 0};
        for (int[] curr : buildings) {
            //curr.start > pre.end
            while (!bases.isEmpty() && curr[0] > pre[1]) {
                int[] next = bases.poll();
                if (next[1] <= pre[1]) {
                    continue; //next.end <= pre.end, continue
                }
                //Add pre.end, next.hight
                result.add(new int[]{pre[1], next[2]});
                pre = next;
            }
            if (curr[2] > pre[2]) { //curr.hight > pre.hight
                if (curr[0] == pre[0]){
                    result.remove(result.size() - 1);
                }
                result.add(new int[]{curr[0], curr[2]});
                if(curr[1] < pre[1]){
                    bases.add(pre);
                }
                pre = curr;
            } else if (curr[1] > pre[1]) {
                if (curr[2] == pre[2]) {
                    pre[1] = curr[1];
                } else {
                    bases.add(curr);
                }
            }
        }
        while (!bases.isEmpty()){
            int[] next = bases.poll();
            if(next[1] <= pre[1]) continue;

            result.add(new int[]{pre[1], next[2]});
            pre = next;
        }
        if (pre[2] > 0) {
            result.add(new int[]{pre[1], 0});
        }
        return result;
    }

解法3 优先堆

 //7ms
 static class heapComparator implements Comparator<int[]> {
        @Override
        public int compare (int[] x, int[] y) {
            if (x[0] == y[0]) {
                return x[1] - y[1];
            }
            return y[0] - x[0];
        }
    }
    public List<List<Integer>> getSkyline(int[][] buildings) {
        PriorityQueue<int[]> queue = new PriorityQueue<>(new heapComparator());
        List<List<Integer>> res = new ArrayList<>();
        int i = 0;
        int n = buildings.length;
        while (i < n || !queue.isEmpty()) {
            int left = 0;
            if (queue.isEmpty() || i < n && buildings[i][0] <= queue.peek()[1]) {
                left = buildings[i][0];
                while (i < n && buildings[i][0] == left) {
                    queue.offer(new int[]{buildings[i][2], buildings[i][1]});
                    i++;
                }
            } else {
                left = queue.peek()[1];
                while (!queue.isEmpty() && queue.peek()[1] <= left) {
                    queue.remove();
                }
            }
            int height = 0;
            if (!queue.isEmpty()) {
                height = queue.peek()[0];
            }
            if (res.size() == 0 || height != res.get(res.size() - 1).get(1)) {
                List<Integer> points = new ArrayList<>();
                points.add(left);
                points.add(height);
                res.add(points);
            }
        }
        return res;
    }

解法4
如果按照一个矩形一个矩形处理会非常麻烦,我们把这些矩形拆成两个点,一个左上角顶点,一个右上角顶点。将所有顶点按照横坐标进行排序然后开始遍历,遍历时通过一个堆来得知当前图形的最高位置,堆顶是所有顶点中最高的点,只要这个点没被移出堆,就说明这个最高的矩形还没结束。对于左顶点,我们将其加入堆中,对于右顶点,我们找出堆中相应的最顶点,然后移出左顶点,同时也意味着这个矩形的结束,为了区分左右顶点,我们以负数作为左顶点,正数作为右顶点
参考:https://blog.csdn.net/qq_37236745/article/details/83795842

    //168ms
    //思考一下为什么这么慢
    public List<List<Integer>> getSkyline(int[][] buildings) {
        
        List<List<Integer>> result = new ArrayList<>();
        List<int[]> height = new ArrayList<>();
        for(int[] b:buildings) {
            height.add(new int[]{b[0], -b[2]});
            height.add(new int[]{b[1], b[2]});
        }
        Collections.sort(height, (a, b) -> {
                if(a[0] != b[0]) 
                    return a[0] - b[0];
                return a[1] - b[1];
        });
        Queue<Integer> pq = new PriorityQueue<>((a, b) -> (b - a));
        pq.offer(0);
        int prev = 0;
        for(int[] h:height) {
            if(h[1] < 0) {
                pq.offer(-h[1]);
            } else {
                pq.remove(h[1]);
            }
            int cur = pq.peek();
            if(prev != cur) {
                result.add(Arrays.asList(h[0], cur));
                prev = cur;
            }
        }
        return result;
    }

解法4

给出一些矩形,求出这些矩形构成的轮廓线,返回轮廓线上每条水平线左边点的坐标集合。

标准的线段树题,而且是带懒惰标记的。
因为坐标可能很大,但是数量却不多,所以首先做离散化处理,将矩形的两个x坐标全部放在一起排序,用map按排序结果编号并记录对应关系。

然后将房子一个个插入树中,因为不要房子右边的点,而且有的房子右边的点可能和其它更低的房子相交,所以右边坐标-1再插入。

高度h作为懒惰标记,当访问到h大于0节点并且要继续向下访问的时候需要向子树更新。

最后逐个节点查询高度值。复杂度O(n*logn),解释见注释。

class node
{
public:
    int l, r, h;
};
class Solution {
public:
    node tree[20005 << 2];
    unordered_map<int, int> mp, mp2;
    void build(int l, int r,int now)
    {
        tree[now].l = l;
        tree[now].r = r;
        tree[now].h = 0;
        if(l == r) return ;
        int mid = (l + r) >> 1;
        build(l, mid, now << 1);
        build(mid + 1, r, now << 1 | 1);
    }
    void insert(int l, int r, int h, int now)
    {
        if(tree[now].h >= h) return ;//无需更新
        if(tree[now].l == l && tree[now].r == r)
        {
            tree[now].h = h;
            //cout<<tree[now].l<<" "<<tree[now].r<<" "<<tree[now].h<<endl;
            return ;
        }
        if(tree[now].h > 0)//懒惰标记
        {
            tree[now << 1].h = max(tree[now].h, tree[now << 1].h);
            tree[now << 1 | 1].h = max(tree[now].h, tree[now << 1 | 1].h);
            tree[now].h = 0;
        }
        int mid = (tree[now].l + tree[now].r) >> 1;
        if(r <= mid) insert(l, r, h, now << 1);
        else if(l > mid) insert(l, r, h, now << 1 | 1);
        else
        {
            insert(l, mid, h, now << 1);
            insert(mid + 1, r, h, now << 1 | 1);
        }
    }
    void query(vector<pair<int, int>> &res, int now)
    {
        if(tree[now].l == tree[now].r)
        {
            if(!res.empty() && tree[now].h == res[res.size() - 1].second) return ;//出去相同高度的相邻点
            pair<int, int> tmp = make_pair(mp2[tree[now].l], tree[now].h);
            res.push_back(tmp);
            return ;
        }
        if(tree[now].h > 0)//懒惰标记
        {
            tree[now << 1].h = max(tree[now].h, tree[now << 1].h);
            tree[now << 1 | 1].h = max(tree[now].h, tree[now << 1 | 1].h);
            tree[now].h = 0;
        }
        query(res, now << 1);
        query(res, now << 1 | 1);
    }
    vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) {
        vector<int> index;
        vector<pair<int, int>> res;
        if(buildings.empty()) return res;//防止空集导致的越界
        for(int i = 0; i < buildings.size(); i++)
        for(int j = 0; j < 2; j++)
        if(!mp[buildings[i][j]])
        {
            mp[buildings[i][j]] = 1;
            index.push_back(buildings[i][j]);
        }
        sort(index.begin(), index.end());
        int n = index.size();
        for(int i = 0; i < n; i++) //离散化,编号-》坐标,坐标-》编号
        {
            mp[index[i]] = i + 1;
            mp2[i + 1] = index[i];
        }
        build(1, n + 1, 1);//建树
        for(int i = 0; i < buildings.size(); i++) insert(mp[buildings[i][0]], mp[buildings[i][1]] - 1, buildings[i][2], 1);//插入房子,线段右边的点不记录,所以右边-1
        query(res, 1);
        return res;
    }
};

线段树题目推荐:http://blog.csdn.net/accepthjp/article/details/28868029

原文:https://blog.csdn.net/accepthjp/article/details/66477033

解法5 二叉搜索树
解析过程:

将输入的每一组数据处理成两个Node结构,结构中是[位置,高度,上升/下降]的信息;
对上面的Node结构数组按照位置进行升序排序;
准备两个TreeMap,第一个htMap存[高度,次数],第二个pmMap存[位置,最大高度] (注意两个Map都是按照key有序的);
遍历Node数组,如果是上升且没有出现目前的,就加入一个(1次),如果出现过,就++;
如果是下降且只有一个了,就移除,否则–;
最后要把每个位置的最大高度存到pmMap中;
最后使用pmMap构造出答案,因为记录了每个位置的最大高度变化,只要前一个高度和当前高度不同,就构造出来
在这里插入图片描述

public class Solution {
    //由一座大楼可以生成两个信息,一个是开始,高度,和上升
    private class Node implements Comparable<Node> {
        public int pos; //position
        public int h; //height
        public boolean isUp;

        public Node(int pos, int h, boolean isUp) {
            this.pos = pos;
            this.h = h;
            this.isUp = isUp;
        }

        @Override
        public int compareTo(Node o) {
            if (pos != o.pos) {
                return pos - o.pos;
            }
            if (isUp != o.isUp) {
                return isUp ? -1 : 1; // 相同的位置下, 向上的排在前面
            }
            return 0;
        }
    }

    public List<List<Integer>> buildingOutline(int[][] buildings) {
        Node[] node = new Node[2 * buildings.length];     // each building to two message
        for (int i = 0; i < buildings.length; i++) {
            node[i * 2] = new Node(buildings[i][0], buildings[i][2], true); //up
            node[i * 2 + 1] = new Node(buildings[i][1], buildings[i][2], false);// down
        }
        Arrays.sort(node); //sorted by start
        TreeMap<Integer, Integer> htMap = new TreeMap<>(); // key : height ; value : times
        TreeMap<Integer, Integer> pmMap = new TreeMap<>(); // key : pos(every) ; value : maxHeight
        for (int i = 0; i < node.length; i++) {
            if (node[i].isUp) {  //if it's up
                if (!htMap.containsKey(node[i].h)) {
                    htMap.put(node[i].h, 1);
                } else {
                    htMap.put(node[i].h, htMap.get(node[i].h) + 1); //add the times
                }
            } else { // down
                if (!htMap.containsKey(node[i].h)) continue;
                if (htMap.get(node[i].h) == 1) {
                    htMap.remove(node[i].h);
                } else {
                    htMap.put(node[i].h, htMap.get(node[i].h) - 1);
                }
            }

            if (htMap.isEmpty()) {
                pmMap.put(node[i].pos, 0);
            } else {
                pmMap.put(node[i].pos, htMap.lastKey()); //存入当前位置和的当前的最大高度
            }
        }

        //根据pmMap 构造出轮廓
        List<List<Integer>> res = new ArrayList<>();
        int start = 0, height = 0; //一开始 = 0
        for (Map.Entry<Integer, Integer> entry : pmMap.entrySet()) {
            int curPosition = entry.getKey();
            int maxHeight = entry.getValue();

            if (height != maxHeight) {    //发现改变  有轮廓
                if (height != 0) {        //这个是第一个要判断一下
                    List<Integer> temp = new ArrayList<>();
                    temp.add(start);    //起点
                    temp.add(curPosition); //当前的
                    temp.add(height);
                    res.add(temp);
                }
                start = curPosition;
                height = maxHeight;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        // 上图的例子
        int[][] buildings = {
                {1, 6, 4},
                {2, 4, 3},
                {5, 8, 5},
                {7, 10, 3}
        };
        System.out.println(new Solution().buildingOutline(buildings));
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值