题目
暴力解法
一般来说,最直白的想法就是:每一个石块落下时,看看和以前落下的石块的哪个有重合区域,如果有重合区域,就会往上面叠。
如果要模拟这个过程的话,如果用数组记录每个石块的两个端点坐标,那数组的范围会开的很大。
我们可以用一个HashMap来缩短这个下标的范围。具体做法就是:下标为i的石块落下后,
这个石块的两个端点之间的高度就放在map中下标为i的位置。从map中取出下标为i的元素,用positions中下标为i的点可以求出第i号点对应的端点,高度可以从map中得到,暴力遍历需要的信息全部可以得到。
用一个数组记录每个石块落下时,当前石块左右两个端点所在的范围内的高度最大值。假设目前已经落下的石块编号用i表示,现在要落下第j个编号的石块。如果j石块位于了第i号石块的范围内,则要更新j石块 (当前石块) 的高度。
边界的处理
一般来说,由左边界+长度=右边界。但如果这样的话,如果下一个石块的左边界==这个石块的右边界的话,就会导致两个石块叠在一起,而实际上是不叠在一起的。
为了处理这种情况,可以在算右边界的时候,令右边界-1;这样的话,可以避免这种情况。
代码
class Solution {
public List<Integer> fallingSquares(int[][] positions) {
int n = positions.length;
List<Integer> heights = new ArrayList<>(n);
// heights.put(0, 0);
for (int i = 0; i < n; i++) {
int left = positions[i][0], right = positions[i][1] + positions[i][0] - 1;
int height = positions[i][1];
for (int j = 0; j < i; j++) {
int left1 = positions[j][0], right1 = positions[j][1] + left1 - 1;
if (left <= right1 && right >= left1) {
height = Math.max(height, heights.get(j) + positions[i][1]);
}
}
heights.add(i, height);
}
for (int i = 1; i < n; i++) {
heights.set(i, Math.max(heights.get(i - 1), heights.get(i)));
}
return heights;
}
}
线段树
上面的暴力解法时间复杂度是 的。很明显,这个题的求解过程和线段树很类似,在求解过程中要查找一个范围内的最大值(如果这个范围内没有石块,则最大值显然为0),然后进行更新。
那么问题就来了:怎样将这个题目和线段树正确的对应起来?
石块的坐标区间是一个范围,这个范围很大,那么有没有什么压缩的方法呢?当然是有的,这个方法就是重点!!!
可以将每一个石块落下来的位置,从小到大,一一对应到整数范围。这样,如果要查询线段树的时候,先通过落下的石块的边界从这个对应关系中得到映射后的整数下标,就可以利用线段树求解了。
这个线段树要支持查询最大值,更新最大值的操作。(更新和查询)
代码
class Solution {
public List<Integer> fallingSquares(int[][] positions) {
HashMap<Integer, Integer> map = index(positions);
int n = map.size();
SegmentTree tree = new SegmentTree(n + 1);
List<Integer> list = new ArrayList<>(n);
int ans = 0;
int len = positions.length;
for (int i = 0; i < len; i++) {
int left = map.get(positions[i][0]);
int right = map.get(positions[i][0] + positions[i][1] - 1);
int max = tree.query(left, right, 1, n, 1) + positions[i][1];
ans = Math.max(ans, max);
tree.update(left, right, max, 1, n, 1);
list.add(ans);
}
return list;
}
HashMap<Integer, Integer> index(int[][] positions) {
int n = positions.length;
TreeSet<Integer> set = new TreeSet<>();
for (int[] pos : positions) {
set.add(pos[0]);
set.add(pos[1] - 1 + pos[0]);
}
int count = 0;
HashMap<Integer, Integer> map = new HashMap<>();
for (Integer item : set) {
map.put(item, ++count);
}
return map;
}
static class SegmentTree{
int[] max;
boolean[] flag;
int[] update;
SegmentTree(int n) {
int N = n << 2;
max = new int[N];
update = new int[N];
flag = new boolean[N];
}
void pushUp(int rt) {
max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]);
}
//update的时候,如果这个区间上有标记,就下放
void pushDown(int rt){
if (!flag[rt]) return;
max[rt << 1] = update[rt];
max[rt << 1 | 1] = update[rt];
update[rt << 1] = update[rt];
update[rt << 1 | 1] = update[rt];
flag[rt << 1] = true;
flag[rt << 1 | 1] = true;
flag[rt] = false;
}
void update(int L, int R, int C, int l, int r, int rt) {
if (L <= l && R >= r) {
update[rt] = C;
max[rt] = C;
flag[rt] = true;
return;
}
int mid = l + ((r - l) >> 1);
pushDown(rt);
if (L <= mid) {
update(L, R, C, l, mid, rt << 1);
}
if (R > mid) {
update(L, R, C, mid + 1, r, rt << 1 | 1);
}
pushUp(rt);
}
int query(int L, int R, int l, int r, int rt) {
if (L <= l && R >= r) return max[rt];
int mid = l + ((r - l) >> 1);
pushDown(rt);
int max = 0;
if (L <= mid) {
max = Math.max(max, query(L, R, l, mid, rt << 1));
}
if (R > mid) {
max = Math.max(max, query(L, R, mid + 1, r, rt << 1 | 1));
}
pushUp(rt);
return max;
}
}
}