【Leetcode】699. Falling Squares

题目地址:

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,ld1]这个区间的信息就行了,这会极大减少线段树的空间消耗,也从而会减少要做的操作的时间消耗)。具体来说,给定一个将要下落的方块,比如该方块的左端点的 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,b1]),我们可以在离散化的时候将方块的中点也加入一起做离散化,但是这会导致中点变成非整数,这里将原坐标乘以 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)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值