【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);    
	}
}

时空复杂度一样。

C++:

class Solution {
 public:
  struct Node {
    int l, r, h;
  };

  vector<Node> tr;

  void pushdown(int u) {
    auto &t = tr[u], &l = tr[u << 1], &r = tr[u << 1 | 1];
    // 注意这里懒标记下传的方式,我们默认懒标记非0,如果非0则直接下传,然后清空;
    // 下传懒标记的含义是当前区间的高度不是一个定值
    if (t.h) {
      l.h = r.h = t.h;
      t.h = 0;
    }
  }

  void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  }

  void update(int u, int l, int r, int h) {
    if (l <= tr[u].l && tr[u].r <= r) {
      tr[u].h = max(tr[u].h, h);
      return;
    }

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) update(u << 1, l, r, h);
    if (r > mid) update(u << 1 | 1, l, r, h);
  }

  vector<vector<int>> getSkyline(vector<vector<int>> &bs) {
  	// 一定要先对高度排序,这样后面的高度才能覆盖掉前面的高度,更新懒标记才能正确
    sort(bs.begin(), bs.end(),
         [&](const vector<int> &v1, const vector<int> &v2) {
           return v1[2] < v2[2];
         });

    vector<int> v;
    for (auto &b : bs) {
      v.push_back(b[0]);
      v.push_back(b[1]);
    }

    sort(v.begin(), v.end());
    int n = v.erase(unique(v.begin(), v.end()), v.end()) - v.begin();
    unordered_map<int, int> mp;
    for (int i = 0; i < n; i++) mp[v[i]] = i;
    tr.resize(n << 2);
    build(1, 0, n);
    // 注意这里的写法,mp[b[1]] - 1
    for (auto &b : bs) update(1, mp[b[0]], mp[b[1]] - 1, b[2]);

    vector<vector<int>> res;
    int last = 0;
    dfs(1, last, v, res);
    return res;
  }

  void dfs(int u, int &last, vector<int> &v, vector<vector<int>> &res) {
    if (tr[u].l == tr[u].r || tr[u].h) {
      if (tr[u].h != last) {
        res.push_back({v[tr[u].l], tr[u].h});
        last = tr[u].h;
      }
      return;
    }
    dfs(u << 1, last, v, res);
    dfs(u << 1 | 1, last, v, res);
  }
};

时空复杂度一样。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值