【Leetcode】218. The Skyline Problem

题目地址:

https://leetcode.com/problems/the-skyline-problem/

给定若干大楼的左右边界及其高度(每个大楼都是矩形),大楼可能有重叠,要求返回一系列点的坐标表示这些大楼的边际线,这些点要从左向右输出,例如,对于相邻的两个点 ( a 1 , h 1 ) , ( a 2 , h 2 ) (a_1,h_1),(a_2,h_2) (a1,h1),(a2,h2),边际线的一部分是 ( a 1 , a 2 , h 1 ) (a_1,a_2,h_1) (a1,a2,h1),代表区间 [ a 1 , a 2 ] [a_1,a_2] [a1,a2]的边际线上的高度是 h 1 h_1 h1。相邻的两个点的高度不应该相同。输入是若干三元组,每个三元组 ( l , r , h ) (l,r,h) (l,r,h)代表 [ l , r ] [l,r] [l,r]这一段有个高 h h h的大楼。例如下图,右图中红色的点的坐标即为所求。
在这里插入图片描述

可以用线段树来做,每次读入一个大楼 b b b,相当于将 [ b [ 0 ] , b [ 1 ] − 1 ] [b[0],b[1]-1] [b[0],b[1]1]这一段的高度变为 b [ 2 ] b[2] b[2]与旧值的更大值,这可以用线段树做。但是注意,由于这题的大楼左右边界的取值范围是 [ 0 , 2 31 − 1 ] [0,2^{31}-1] [0,2311],所以要采用动态开点的方式,而不能一下子把所有节点都开出来,这样,只有叶子才是真正有效的值。并且,如果某个节点的左右儿子都是叶子,且它们的高度相同,则可以将这两个叶子删除,自己高度更新,并且成为新的叶子,这样可以减少节点个数。最后只需要DFS一遍就能知道边界。代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    
    
    
    class Node {
    
    
        int st, ed, h;
        Node left, right;
        
        public Node(int st, int ed, int h) {
    
    
            this.st = st;
            this.ed = ed;
            this.h = h;
        }
    }
    
    // 如果cur的左右儿子都是叶子并且高度相同,则删除左右儿子,自己高度更新,成为新的叶子
    void pushup(Node cur) {
    
    
        if (cur.left != null && cur.right != null) {
    
    
            if (cur.left.left == null && cur.right.left == null && cur.left.h == cur.right.h) {
    
    
                cur.h = cur.left.h;
                cur.left = cur.right = null;
            }
        }
    }
    
    void update(Node root, int st, int ed, int h) {
    
    
        if (root == null) {
    
    
            return;
        }
        
        if (root.st > ed || root.ed < st) {
    
    
            return;
        }
        
        // 如果是叶子,看一下要不要分裂,如果不需要则直接赋值;否则分裂
        if (root.left == null && root.right == null) {
    
    
            if (st <= root.st && root.ed <= ed) {
    
    
                root.h = Math.max(root.h, h);
                return;
            }
            
            // root.st, st - 1    st, root.ed
            if (root.st < st) {
    
    
                root.left = new Node(root.st, st - 1, root.h);
                root.right = new Node(st, root.ed, root.h);
                update(root.right, st, ed, h);
            } else {
    
    
                root.left = new Node(root.st, ed, root.h);
                root.right = new Node(ed + 1, root.ed, root.h);
                update(root.left, st, ed, h);
            }
        } else {
    
    
        	// 如果不是叶子,则直接更新左右儿子
            update(root.left, st, ed, h);
            update(root.right, st, ed, h);
        }
        
        pushup(root);
    }
    
    Node root;
    int last;
    
    void dfs(Node cur, List<List<Integer>> res) {
    
    
        if (cur == null) {
    
    
            return;
        }
        
        dfs(cur.left, res);
        if (cur.left == null && cur.right == null && cur.h != last) {
    
    
            res.add(Arrays.asList(cur.st, cur.h));
            last = cur.h;
        }
        dfs(cur.right, res);
    }
    
    public List<List<Integer>> getSkyline(int[][] buildings) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        root = new Node(0, Integer.MAX_VALUE, 0);
        for (int[] b : buildings) {
    
    
            update(root, b[0], b[1] - 1, b[2]);
        }
        
        dfs(root, res);
        return res;
    }
}

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)

下面给出离散化的做法。由于区间的取值范围特别大,但区间的个数很少,所以想到离散化的方法。先将所有端点排序,然后去重,接着用静态开点的办法,一次性开出所有点。每次更新的时候,需要二分出离散化后的下标是什么然后在线段树里更新。先将大楼的区间按照高度从小到大排序,接着线段树事实上只是在执行将某段区间变为 x x x的操作,可以带懒标记做。代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {
    
    
    
    class Node {
    
    
    	// h是懒标记,表示当前区间要变为h
        int l, r, h;
        
        public Node(int l, int r) {
    
    
            this.l = l;
            this.r = r;
        }
    }
    
    Node[] tr;
    
    void pushdown(int u) {
    
    
        if (tr[u].h > 0) {
    
    
            tr[u << 1].h = tr[u << 1 | 1].h = tr[u].h;
            tr[u].h = 0;
        }
    }
    
    void build(int u, int l, int r) {
    
    
        tr[u] = new Node(l, r);
        if (l == r) {
    
    
            return;
        }
        
        int mid = l + (r - l >> 1);
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
    
    void modify(int u, int l, int r, int h) {
    
    
        if (l <= tr[u].l && tr[u].r <= r) {
    
    
            tr[u].h = h;
            return;
        }
        
        pushdown(u);
        int mid = tr[u].l + (tr[u].r - tr[u].l >> 1);
        if (l <= mid) {
    
    
            modify(u << 1, l, r, h);
        }
        if (r > mid) {
    
    
            modify(u << 1 | 1, l, r, h);
        }
    }
    
    public List<List<Integer>> getSkyline(int[][] buildings) {
    
    
        Arrays.sort(buildings, (b1, b2) -> Integer.compare(b1[2], b2[2]));
        int[] idx = new int[buildings.length << 1];
        int r = 0;
        for (int[] b : buildings) {
    
    
            idx[r++] = b[0];
            idx[r++] = b[1];
        }
        
        Arrays.sort(idx);
        r = 0;
        for (int i = 0; i < idx.length; i++) {
    
    
            if (r == 0 || idx[i] != idx[r - 1]) {
    
    
                idx[r++] = idx[i];
            }
        }
        
        tr = new Node[r + 1 << 2];
        build(1, 0, r);
        for (int[] b : buildings) {
    
    
            modify(1, Arrays.binarySearch(idx, 0, r, b[0]), Arrays.binarySearch(idx, 0, r, b[1]) - 1, b[2]);
        }
        
        List<List<Integer>> res = new ArrayList<>();
        dfs(1, res, idx);
        return res;
    }
    
    int last;
    
    void dfs(int u, List<List<Integer>> res, int[] idx) {
    
    
    	// 如果走到叶子,或者当前节点有懒标记,则考虑将该线段的左端点及其高度加入答案
        if (tr[u].l == tr[u].r || tr[u].h > 0) {
    
    
            if (tr[u].h != last) {
    
    
                res.add(Arrays.asList(idx[tr[u].l], tr[u].h));
            }
            
            last = tr[u].h;
            return;
        }
        
        dfs(u << 1, res, idx);
        dfs(u << 1 | 1, res, idx);    
	}
}

时空复杂度一样。

Guess you like

Origin blog.csdn.net/qq_46105170/article/details/121259936