Leetcode699. 掉落的石块 [线段树]

152 阅读1分钟

699. 掉落的方块 - 力扣(LeetCode)

题目

image.png

image.png

image.png

暴力解法

一般来说,最直白的想法就是:每一个石块落下时,看看和以前落下的石块的哪个有重合区域,如果有重合区域,就会往上面叠。

如果要模拟这个过程的话,如果用数组记录每个石块的两个端点坐标,那数组的范围会开的很大。 我们可以用一个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;
    }
}

线段树

上面的暴力解法时间复杂度是O(n2)O(n^2) 的。很明显,这个题的求解过程和线段树很类似,在求解过程中要查找一个范围内的最大值(如果这个范围内没有石块,则最大值显然为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;
            
        }
    }
}