题目地址:
https://leetcode.com/problems/falling-squares/
给定一个数学里的二维平面直角坐标系 x O y xOy xOy,想象有若干正方形方块掉落,题目保证正方形方块的左下端点的坐标是正整数,也保证方块边长是正整数。想象 x x x轴是地面,如果某个方块掉落的过程中遇到了之前的某个方块(擦边而过不算),则该方块会叠到上面。现在给定一个长 n n n数组 A A A, A [ i ] A[i] A[i]存了第 i i i个掉落的方块的信息,其中 A [ i ] [ 0 ] A[i][0] A[i][0]表示它的左下角的 x x x坐标, A [ i ] [ 1 ] A[i][1] A[i][1]表示它的边长。要求返回一个长 n n n数组 B B B,使得 B [ i ] B[i] B[i]表示在 A [ i ] A[i] A[i]掉落之后,当前所有方块的最高点的 y y y坐标。
思路是线段树 + 离散化。可以将 x x x坐标离散化,这样可以节省存储空间(离散化的过程其实就是将一个数组 d d d排序后去重,然后将每个数映射到它的下标。这样在线段树建树的时候,就只需维护 [ 0 , l d − 1 ] [0,l_d-1] [0,ld−1]这个区间的信息就行了,这会极大减少线段树的空间消耗,也从而会减少要做的操作的时间消耗)。具体来说,给定一个将要下落的方块,比如该方块的左端点的 x x x坐标和右端点的 x x x坐标分别是 a a a和 b b b,边长是 c c c,那么我们需要实现两个操作,第一是查询 ( a , b ) (a,b) (a,b)里的最大值 M M M(注意这里查询的是开区间 ( a , b ) (a,b) (a,b)的最大值,因为下落的方块擦着另一个方块的边的话,是不会叠上去的),另一个是将 [ a , b ] [a,b] [a,b]里所有值都变成 M + c M+c M+c。本质上是要求一个数据结构可以查询区间最大值,以及将区间修改为某一值,这可以用线段树 + 懒标记来做到。在离散化之后,为了使得区间 ( a , b ) (a,b) (a,b)非空(注意这里 a a a和 b b b都是离散化之后的值,此时 ( a , b ) = [ a + 1 , b − 1 ] (a,b)=[a+1,b-1] (a,b)=[a+1,b−1]),我们可以在离散化的时候将方块的中点也加入一起做离散化,但是这会导致中点变成非整数,这里将原坐标乘以 2 2 2就行了。代码如下:
import java.util.ArrayList;
import java.util.List;
public class Solution {
// 实现一下带懒标记的线段树
class SegTree {
// v是[l, r]区间的最大值,lazy是懒标记
class Node {
int l, r, v, lazy;
public Node(int l, int r) {
this.l = l;
this.r = r;
}
}
private Node[] tr;
public SegTree(int size) {
tr = new Node[size << 2];
build(1, 0, size - 1);
}
private void pushup(int u) {
tr[u].v = Math.max(tr[u << 1].v, tr[u << 1 | 1].v);
}
// 下传懒标记
private void pushdown(int u) {
int c = tr[u].lazy;
if (c != 0) {
tr[u].lazy = 0;
tr[u << 1].v = tr[u << 1 | 1].v = c;
tr[u << 1].lazy = tr[u << 1 | 1].lazy = c;
}
}
public void build(int u, int l, int r) {
tr[u] = new Node(l, r);
if (l == r) {
return;
}
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
public void update(int u, int l, int r, int c) {
// 任务不需要下发,可以用懒标记懒住
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].v = tr[u].lazy = c;
return;
}
// 任务不得不下发,则先下发给两个孩子
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) {
update(u << 1, l, r, c);
}
if (mid + 1 <= r) {
update(u << 1 | 1, l, r, c);
}
// 孩子完成了任务,再修改自己的值
pushup(u);
}
public int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].v;
}
pushdown(u);
int res = 0, mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) {
res = Math.max(res, query(u << 1, l, r));
}
if (mid + 1 <= r) {
res = Math.max(res, query(u << 1 | 1, l, r));
}
return res;
}
public int query() {
return tr[1].v;
}
}
public List<Integer> fallingSquares(int[][] positions) {
List<Integer> xs = new ArrayList<>();
for (int[] p : positions) {
int a = p[0], b = a + p[1];
xs.add(a * 2);
xs.add(b * 2);
xs.add(a + b);
}
// 排序并去重
xs = unique(xs);
SegTree segTree = new SegTree(xs.size());
List<Integer> res = new ArrayList<>();
for (int[] p : positions) {
int a = p[0], b = a + p[1];
a = get(a * 2, xs);
b = get(b * 2, xs);
int h = segTree.query(1, a + 1, b - 1);
segTree.update(1, a, b, h + p[1]);
res.add(segTree.query());
}
return res;
}
// 找到x在离散化之后的值是多少,其实就是求xs里x的下标,可以二分来找到
private int get(int x, List<Integer> xs) {
int l = 0, r = xs.size() - 1;
while (l < r) {
int m = l + r >> 1;
if (xs.get(m) >= x) {
r = m;
} else {
l = m + 1;
}
}
return l;
}
// 将list排序后去重
private List<Integer> unique(List<Integer> list) {
list.sort(Integer::compareTo);
int j = 0;
for (int i = 0; i < list.size(); i++) {
if (i == 0 || list.get(j - 1) != list.get(i)) {
list.set(j++, list.get(i));
}
}
return list.subList(0, j);
}
}
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)。