题目地址:
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,231−1],所以要采用动态开点的方式,而不能一下子把所有节点都开出来,这样,只有叶子才是真正有效的值。并且,如果某个节点的左右儿子都是叶子,且它们的高度相同,则可以将这两个叶子删除,自己高度更新,并且成为新的叶子,这样可以减少节点个数。最后只需要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);
}
}
时空复杂度一样。