线段树练习题(日程安排表、LC-307、LC-2407、LC-699)

news2024/11/22 15:15:43

线段树详解:https://leetcode.cn/problems/range-module/solution/by-lfool-eo50/

文章目录

  • 线段树
    • 线段树模板
    • [729. 我的日程安排表 I](https://leetcode.cn/problems/my-calendar-i/)
    • [731. 我的日程安排表 II](https://leetcode.cn/problems/my-calendar-ii/)
    • [732. 我的日程安排表 III](https://leetcode.cn/problems/my-calendar-iii/)
    • 🔺[715. Range 模块](https://leetcode.cn/problems/range-module/)
    • [307. 区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/)
    • [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/)
    • [2407. 最长递增子序列 II](https://leetcode.cn/problems/longest-increasing-subsequence-ii/)
    • [933. 最近的请求次数](https://leetcode.cn/problems/number-of-recent-calls/)
    • [699. 掉落的方块](https://leetcode.cn/problems/falling-squares/)

线段树

729. 我的日程安排表 I

731. 我的日程安排表 II

732. 我的日程安排表 III

715. Range 模块

307. 区域和检索 - 数组可修改

933. 最近的请求次数

699. 掉落的方块

2407. 最长递增子序列 II

线段树模板

模板:注意:下面模版基于求「区间和」以及对区间进行「加减」的更新操作,且为「动态开点」

public class SegmentTreeDynamic {
    class Node {
        Node left, right;
        int val, add;
    }
    private int N = (int) 1e9;
    private Node root = new Node();
	//初始值start和end是固定的0-N,l和r是要更新的区间,更新值为val
    public void update(Node node, int start, int end, int l, int r, int val) {
        if (l <= start && end <= r) {
            node.val += (end - start + 1) * val;
            node.add += val;
            return ;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) update(node.left, start, mid, l, r, val);
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }
	
    public int query(Node node, int start, int end, int l, int r) {
        if (l <= start && end <= r) return node.val;
        int mid = (start + end) >> 1, ans = 0;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) ans += query(node.left, start, mid, l, r);
        if (r > mid) ans += query(node.right, mid + 1, end, l, r);
        return ans;
    }
	
    private void pushUp(Node node) {
        node.val = node.left.val + node.right.val;
    }
	
    private void pushDown(Node node, int leftNum, int rightNum) {
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        if (node.add == 0) return ;
        node.left.val += node.add * leftNum;
        node.right.val += node.add * rightNum;
        // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖
        node.left.add += node.add;
        node.right.add += node.add;
        node.add = 0;
    }
    
}

729. 我的日程安排表 I

难度中等256

实现一个 MyCalendar 类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ,则可以存储这个新的日程安排。

当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生 重复预订

日程可以用一对整数 startend 表示,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end

实现 MyCalendar 类:

  • MyCalendar() 初始化日历对象。
  • boolean book(int start, int end) 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true 。否则,返回 false 并且不要将该日程安排添加到日历中。

示例:

输入:
["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
输出:
[null, true, false, true]

解释:
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。
myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。

提示:

  • 0 <= start < end <= 109
  • 每个测试用例,调用 book 方法的次数最多不超过 1000 次。

题解:问题是查看区间是否被占用,等同于查看区间最值,如果被占用了,则区间最大值为1,为0即表示区间没有被占用。每次book的时候设置区间值都为1

class MyCalendar {

    public MyCalendar() {

    }
    
    public boolean book(int start, int end) {
        int exist = query(root, 0, N, start, end-1);
        if(exist == 1) return false;
        update(root, 0, N, start, end-1, 1);
        return true;
    }

    class Node{
        Node left, right;
        int val, add;
    }
    private int N = (int)1e9;
    private Node root = new Node();

    public void update(Node node, int start, int end, int l, int r, int val){
        if(l <= start && r >= end){
            node.val = val;
            node.add = val;
            return; 
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        if(l <= mid) update(node.left, start, mid, l, r, val);
        if(r > mid) update(node.right, mid+1, end, l, r, val);
        pushUp(node);
    }

    public int query(Node node, int start, int end, int l, int r){
        if(l <= start && r >= end){
            return node.val;
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        int ans = 0;
        if(l <= mid) ans = Math.max(ans, query(node.left, start, mid, l, r));
        if(r > mid) ans = Math.max(ans, query(node.right, mid+1, end, l, r));
        return ans;
    }

    public void pushDown(Node node){
        if(node.left == null) node.left = new Node();
        if(node.right == null) node.right = new Node();
        if(node.add == 0) return;
        node.left.val = node.add;
        node.right.val = node.add;
        node.left.add = node.add;
        node.right.add = node.add;
        node.add = 0;
    }

    public void pushUp(Node node){
        node.val = Math.max(node.left.val, node.right.val);
    }
}


731. 我的日程安排表 II

难度中等218

实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。

MyCalendar 有一个 book(int start, int end)方法。它意味着在 startend 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end

当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。

每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。

请按照以下步骤调用MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

示例:

MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(50, 60); // returns true
MyCalendar.book(10, 40); // returns true
MyCalendar.book(5, 15); // returns false
MyCalendar.book(5, 10); // returns true
MyCalendar.book(25, 55); // returns true
解释: 
前两个日程安排可以添加至日历中。 第三个日程安排会导致双重预订,但可以添加至日历中。
第四个日程安排活动(5,15)不能添加至日历中,因为它会导致三重预订。
第五个日程安排(5,10)可以添加至日历中,因为它未使用已经双重预订的时间10。
第六个日程安排(25,55)可以添加至日历中,因为时间 [25,40] 将和第三个日程安排双重预订;
时间 [40,50] 将单独预订,时间 [50,55)将和第二个日程安排双重预订。

提示:

  • 每个测试用例,调用 MyCalendar.book 函数最多不超过 1000次。
  • 调用函数 MyCalendar.book(start, end)时, startend 的取值范围为 [0, 10^9]

题解:问题同日程安排表1,区间最值 < 2

class MyCalendarTwo {

    public MyCalendarTwo() {
    }
    
    public boolean book(int start, int end) {
        // 不会导致三重预定,也就是区间最多update2次,query < 2
        if(query(root, 0, N, start, end-1) < 2){
            update(root, 0, N, start, end-1, 1);
            return true;
        }else{
            return false;
        }
    }

    class Node{
        Node left, right;
        int val, add;
    }

    private int N = (int)1e9;
    private Node root = new Node();

    public void update(Node node, int start, int end, int l, int r, int val){
        if(l <= start && r >= end){
            node.val += val;
            node.add += val;
            return;
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        if(l <= mid) update(node.left, start, mid, l, r, val);
        if(r > mid) update(node.right, mid+1, end, l, r, val);
        pushUp(node); 
    }

    public int query(Node node, int start, int end, int l, int r){
        if(l <= start && r >= end){
            return node.val;
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        int res = 0;
        if(l <= mid) res = Math.max(res, query(node.left, start, mid, l, r));
        if(r > mid) res = Math.max(res, query(node.right, mid+1, end, l, r));
        return res;
    }

    public void pushDown(Node node){
        if(node.left == null) node.left = new Node();
        if(node.right == null) node.right = new Node();
        if(node.add == 0) return;
        node.left.val += node.add;
        node.right.val += node.add;
        node.left.add += node.add;
        node.right.add += node.add;
        node.add = 0;
    }

    public void pushUp(Node node){
        node.val = Math.max(node.left.val, node.right.val);
    }
}

732. 我的日程安排表 III

难度困难203

k 个日程安排有一些时间上的交叉时(例如 k 个日程安排都在同一时间内),就会产生 k 次预订。

给你一些日程安排 [start, end) ,请你在每个日程安排添加后,返回一个整数 k ,表示所有先前日程安排会产生的最大 k 次预订。

实现一个 MyCalendarThree 类来存放你的日程安排,你可以一直添加新的日程安排。

  • MyCalendarThree() 初始化对象。
  • int book(int start, int end) 返回一个整数 k ,表示日历中存在的 k 次预订的最大值。

示例:

输入:
["MyCalendarThree", "book", "book", "book", "book", "book", "book"]
[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]
输出:
[null, 1, 1, 2, 3, 3, 3]

解释:
MyCalendarThree myCalendarThree = new MyCalendarThree();
myCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
myCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
myCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。
myCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。
myCalendarThree.book(5, 10); // 返回 3
myCalendarThree.book(25, 55); // 返回 3

提示:

  • 0 <= start < end <= 109
  • 每个测试用例,调用 book 函数最多不超过 400

题解:同日程安排表Ⅱ,返回最大值(统计区间最值,对区间进行加减操作)

class MyCalendarThree {

    public MyCalendarThree() {

    }
    
    public int book(int startTime, int endTime) {
        update(root, 0, N, startTime, endTime-1, 1);
        return query(root, 0, N, 0, N);
    }

    class Node{
        Node left, right;
        int val, add;
    }

    private int N = (int)1e9;
    private Node root = new Node();

    public void update(Node node, int start, int end, int l, int r, int val){
        if(l <= start && r >= end){
            node.val += val;
            node.add += val;
            return;
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        if(l <= mid) update(node.left, start, mid, l, r, val);
        if(r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }

    public int query(Node node, int start, int end, int l, int r){
        if(l <= start && r >= end){
            return node.val;
        }
        int mid = (start + end) >> 1;
        pushDown(node);
        int res = 0;
        if(l <= mid) res = Math.max(res, query(node.left, start, mid, l, r));
        if(r > mid) res = Math.max(res, query(node.right, mid + 1, end, l, r));
        return res;
    }

    private void pushDown(Node node){
        if(node.left == null) node.left = new Node();
        if(node.right == null) node.right = new Node();
        if(node.add == 0) return;
        node.left.val += node.add;
        node.right.val += node.add;
        node.left.add += node.add;
        node.right.add += node.add;
        node.add = 0; 
    }

    private void pushUp(Node node){
        node.val = Math.max(node.left.val, node.right.val);
    }

}

🔺715. Range 模块

难度困难216

Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。

半开区间 [left, right) 表示所有 left <= x < right 的实数 x

实现 RangeModule 类:

  • RangeModule() 初始化数据结构的对象。
  • void addRange(int left, int right) 添加 半开区间 [left, right),跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 [left, right) 中尚未跟踪的任何数字到该区间中。
  • boolean queryRange(int left, int right) 只有在当前正在跟踪区间 [left, right) 中的每一个实数时,才返回 true ,否则返回 false
  • void removeRange(int left, int right) 停止跟踪 半开区间 [left, right) 中当前正在跟踪的每个实数。

示例 1:

输入
["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "queryRange"]
[[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]]
输出
[null, null, null, true, false, true]

解释
RangeModule rangeModule = new RangeModule();
rangeModule.addRange(10, 20);
rangeModule.removeRange(14, 16);
rangeModule.queryRange(10, 14); 返回 true (区间 [10, 14) 中的每个数都正在被跟踪)
rangeModule.queryRange(13, 15); 返回 false(未跟踪区间 [13, 15) 中像 14, 14.03, 14.17 这样的数字)
rangeModule.queryRange(16, 17); 返回 true (尽管执行了删除操作,区间 [16, 17) 中的数字 16 仍然会被跟踪)

提示:

  • 1 <= left < right <= 109
  • 在单个测试用例中,对 addRangequeryRangeremoveRange 的调用总数不超过 104

题解:每个节点的值表示当前区间是否被覆盖

class RangeModule {

    public RangeModule() {

    }
    
    public void addRange(int left, int right) {
        // 1 表示复盖;-1 表示取消覆盖
        update(root, 0, N, left, right-1, 1);
    }
    
    public boolean queryRange(int left, int right) {
        return query(root, 0, N, left, right-1);
    }
    
    public void removeRange(int left, int right) {
        update(root, 0, N, left, right-1, -1);
    }


    class Node{
        Node left, right;
        boolean cover; // 表示当前区间是否被覆盖
        int add;
    }

    private int N = (int)1e9;
    private Node root = new Node();

    public void update(Node node, int start, int end, int l, int r, int val){
        if(l <= start && r >= end){
            node.cover = val == 1;
            node.add = val;
            return;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        if(l <= mid) update(node.left, start, mid, l, r, val);
        if(r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }

    public boolean query(Node node, int start, int end, int l, int r){
        if(l <= start && r >= end){
            return node.cover;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        // 查询左右子树是否被覆盖
        boolean res = true;
        if(l <= mid) res &= query(node.left, start, mid, l, r);
        if(r > mid) res &= query(node.right, mid + 1, end, l, r);
        return res;
    }

    private void pushDown(Node node,int leftNum, int rightNum){
        if(node.left == null) node.left = new Node();
        if(node.right == null) node.right = new Node();
        if(node.add == 0) return;
        node.left.cover = node.add == 1;
        node.right.cover = node.add == 1;
        node.left.add = node.add;
        node.right.add = node.add;
        node.add = 0; 
    }

    private void pushUp(Node node){
        node.cover = node.left.cover && node.right.cover;
    }
}

307. 区域和检索 - 数组可修改

难度中等597

给你一个数组 nums ,请你完成两类查询。

  1. 其中一类查询要求 更新 数组 nums 下标对应的值
  2. 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 用整数数组 nums 初始化对象
  • void update(int index, int val)nums[index] 的值 更新val
  • int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 (即,nums[left] + nums[left + 1], ..., nums[right]

示例 1:

输入:
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:
[null, 9, null, 8]

解释:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9
numArray.update(1, 2);   // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8

提示:

  • 1 <= nums.length <= 3 * 104
  • -100 <= nums[i] <= 100
  • 0 <= index < nums.length
  • -100 <= val <= 100
  • 0 <= left <= right < nums.length
  • 调用 updatesumRange 方法次数不大于 3 * 104
class NumArray {

    public NumArray(int[] nums) {
        this.N = nums.length - 1;
        for(int i = 0; i <= N; i++){
            update(root, 0, N, i, i, nums[i]);
        }
    }
    
    public void update(int index, int val) {
        update(root, 0, N, index, index, val);
    }
    
    public int sumRange(int left, int right) {
        return query(root, 0, N, left, right);
    }

    class Node {
        Node left, right;
        int val, add;
    }
    private int N;
    private Node root = new Node();


	//初始值start和end是固定的0-N,l和r是要更新的区间,更新值为val
    public void update(Node node, int start, int end, int l, int r, int val) {
        if (l <= start && end <= r) {
            node.val = (end - start + 1) * val;
            node.add = val;
            return;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) update(node.left, start, mid, l, r, val);
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }
	
    public int query(Node node, int start, int end, int l, int r) {
        if (l <= start && end <= r) return node.val;
        int mid = (start + end) >> 1, ans = 0;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) ans += query(node.left, start, mid, l, r);
        if (r > mid) ans += query(node.right, mid + 1, end, l, r);
        return ans;
    }
	
    private void pushUp(Node node) {
        node.val = node.left.val + node.right.val;
    }
	
    private void pushDown(Node node, int leftNum, int rightNum) {
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        if (node.add == 0) return ;
        node.left.val = node.add * leftNum;
        node.right.val = node.add * rightNum;
        // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖
        node.left.add = node.add;
        node.right.add = node.add;
        node.add = 0;
    }
}

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * obj.update(index,val);
 * int param_2 = obj.sumRange(left,right);
 */

933. 最近的请求次数

难度简单214

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例 1:

输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示:

  • 1 <= t <= 109
  • 保证每次对 ping 调用所使用的 t 值都 严格递增
  • 至多调用 ping 方法 104

2407. 最长递增子序列 II

难度困难60

给你一个整数数组 nums 和一个整数 k

找到 nums 中满足以下要求的最长子序列:

  • 子序列 严格递增
  • 子序列中相邻元素的差值 不超过 k

请你返回满足上述要求的 最长子序列 的长度。

子序列 是从一个数组中删除部分元素后,剩余元素不改变顺序得到的数组。

示例 1:

输入:nums = [4,2,1,4,3,4,5,8,15], k = 3
输出:5
解释:
满足要求的最长子序列是 [1,3,4,5,8] 。
子序列长度为 5 ,所以我们返回 5 。
注意子序列 [1,3,4,5,8,15] 不满足要求,因为 15 - 8 = 7 大于 3 。

示例 2:

输入:nums = [7,4,5,1,8,12,4,7], k = 5
输出:4
解释:
满足要求的最长子序列是 [4,5,8,12] 。
子序列长度为 4 ,所以我们返回 4 。

示例 3:

输入:nums = [1,5], k = 1
输出:1
解释:
满足要求的最长子序列是 [1] 。
子序列长度为 1 ,所以我们返回 1 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i], k <= 105

线段树解法的最长递增子序列,状态从j-k < j' < j 而不是0 < j' < j转移过来

class Solution {	
    public int lengthOfLIS(int[] nums, int k) {
        for(int num : nums){
            num += (int)1e4; // -104 <= nums[i] <= 104,都变成正数
            int startidx = Math.max(num - k, 1);
            // 查找以元素值(1,num-1)结尾的LIS的最大值
            int res = 1 + query(root,1,N,startidx,num-1);
            update(root,1,N,num,num,res);// 更新为前面最大值 + 1
        }
        // 最后返回区间最大值
        return query(root,1,N,1,N);
    }

    class Node {
        // 左右孩子节点
        Node left, right;
        // 当前节点值,以及懒惰标记的值
        int val, add;
    }
    private int N = (int) 1e9;
    private Node root = new Node();
    public void update(Node node, int start, int end, int l, int r, int val) {
        if (l <= start && end <= r) {
            node.val = val;
            node.add = val;
            return ;
        }
        pushDown(node);
        int mid = (start + end) >> 1;
        if (l <= mid) update(node.left, start, mid, l, r, val);
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }
    public int query(Node node, int start, int end, int l, int r) {
        if (l <= start && end <= r) return node.val;
        pushDown(node);
        int mid = (start + end) >> 1, ans = 0;
        if (l <= mid) ans = query(node.left, start, mid, l, r);
        if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));
        return ans;
    }
    private void pushUp(Node node) {
        // 每个节点存的是当前区间的最大值
        node.val = Math.max(node.left.val, node.right.val);
    }
    private void pushDown(Node node) {
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        if (node.add == 0) return ;
        node.left.val = node.add;
        node.right.val = node.add;
        node.left.add = node.add;
        node.right.add = node.add;
        node.add = 0;
    }
}

933. 最近的请求次数

难度简单214

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例 1:

输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示:

  • 1 <= t <= 109
  • 保证每次对 ping 调用所使用的 t 值都 严格递增
  • 至多调用 ping 方法 104
class RecentCounter {

    SegmentTreeDynamic t;

    public RecentCounter() {
        t = new SegmentTreeDynamic();
    }
    
    public int ping(int val) {
        t.update(t.root, 1, t.N, val, val, 1);
        return t.query(t.root, 1, t.N, Math.max(0, val - 3000), val);
    }
}

public class SegmentTreeDynamic {
    class Node {
        Node left, right;
        int val, add;
    }
    int N = (int) 1e9;
    Node root = new Node();
	//初始值start和end是固定的0-N,l和r是要更新的区间,更新值为val
    public void update(Node node, int start, int end, int l, int r, int val) {
        if (l <= start && end <= r) {
            node.val += (end - start + 1) * val;
            node.add += val;
            return ;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) update(node.left, start, mid, l, r, val);
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }
	
    public int query(Node node, int start, int end, int l, int r) {
        if (l <= start && end <= r) return node.val;
        int mid = (start + end) >> 1, ans = 0;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) ans += query(node.left, start, mid, l, r);
        if (r > mid) ans += query(node.right, mid + 1, end, l, r);
        return ans;
    }
	
    private void pushUp(Node node) {
        node.val = node.left.val + node.right.val;
    }
	
    private void pushDown(Node node, int leftNum, int rightNum) {
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        if (node.add == 0) return;
        node.left.val += node.add * leftNum;
        node.right.val += node.add * rightNum;
        // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖
        node.left.add += node.add;
        node.right.add += node.add;
        node.add = 0;
    }
    
}

699. 掉落的方块

难度困难180

在二维平面上的 x 轴上,放置着一些方块。

给你一个二维整数数组 positions ,其中 positions[i] = [lefti, sideLengthi] 表示:第 i 个方块边长为 sideLengthi ,其左侧边与 x 轴上坐标点 lefti 对齐。

每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。

在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度

返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。

示例 1:

img

输入:positions = [[1,2],[2,3],[6,1]]
输出:[2,5,5]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 2 。
第 2 个方块掉落后,最高的堆叠由方块 1 和 2 组成,堆叠的最高高度为 5 。
第 3 个方块掉落后,最高的堆叠仍然由方块 1 和 2 组成,堆叠的最高高度为 5 。
因此,返回 [2, 5, 5] 作为答案。

示例 2:

输入:positions = [[100,100],[200,100]]
输出:[100,100]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 100 。
第 2 个方块掉落后,最高的堆叠可以由方块 1 组成也可以由方块 2 组成,堆叠的最高高度为 100 。
因此,返回 [100, 100] 作为答案。
注意,方块 2 擦过方块 1 的右侧边,但不会算作在方块 1 上着陆。

提示:

  • 1 <= positions.length <= 1000
  • 1 <= lefti <= 108
  • 1 <= sideLengthi <= 106
class Solution {
    public List<Integer> fallingSquares(int[][] positions) {
        // 求区间最值 区间加减操作
        List<Integer> res = new ArrayList<>();
        for(int[] p : positions){
            int left = p[0], right = p[0] + p[1], val = p[1];
            //先查询出 [x, x + h] 的值
            int cur = query(root, 0, N, left, right-1);
            //更新 [x, x + h - 1] 为 cur + h
            update(root, 0, N, left, right-1, val + cur);
            res.add(query(root, 0, N, 0, N));
        }
        //ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。
        return res;
    }

    class Node {
        Node left, right;
        int val, add;
    }
    private int N = (int) 1e9;
    private Node root = new Node();
	//初始值start和end是固定的0-N,l和r是要更新的区间,更新值为val
    public void update(Node node, int start, int end, int l, int r, int val) {
        if (l <= start && end <= r) {
            node.val = val;
            node.add = val;
            return ;
        }
        int mid = (start + end) >> 1;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) update(node.left, start, mid, l, r, val);
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        pushUp(node);
    }
	
    public int query(Node node, int start, int end, int l, int r) {
        if (l <= start && end <= r) return node.val;
        int mid = (start + end) >> 1, ans = 0;
        pushDown(node, mid - start + 1, end - mid);
        if (l <= mid) ans = Math.max(ans, query(node.left, start, mid, l, r));
        if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));
        return ans;
    }
	
    private void pushUp(Node node) {
        node.val = Math.max(node.left.val, node.right.val);
    }
	
    private void pushDown(Node node, int leftNum, int rightNum) {
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        if (node.add == 0) return;
        node.left.val = node.add;
        node.right.val = node.add;
        // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖
        node.left.add = node.add;
        node.right.add = node.add;
        node.add = 0;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库基础及用户管理授权

数据库概念 关系型数据库 数据结构二维表格 库 -> 表 -> 列&#xff08;字段&#xff09;&#xff1a;用来描述对象的的一个属性&#xff1b;行&#xff1a;用来描述一个对象的信息 mysql&#xff08;5.7/8.0&#xff09; maridb ocracle postgresql sqlserver(windows…

2D火焰特效

Unity面片实现火焰效果 一、效果说明 大家好&#xff0c;我是阿赵。这是一个火焰的效&#xff0c;不过它不是粒子做的&#xff0c;是用一个面片做的&#xff0c;可以理解成是2D的特效。这个例子很简单&#xff0c;但可以拓展一下思路&#xff0c;原来除了用序列帧和粒子做动画…

将页面元素隐藏的10种方法

在Web开发中&#xff0c;隐藏页面元素使其视觉不可见是一个非常常见的需求。为了实现这一目标&#xff0c;我们通常会采用多种方法&#xff0c;最常用的例如CSS的display属性&#xff0c;只要设置为node即可隐藏元素。 本文将通过对当前所有可用的隐藏元素的方法做一个总结&…

Flink系列-11、Flink DataStream的Sink

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 大数据系列文章目录 官方网址&#xff1a;https://flink.apache.org/ 学习资料&#xff1a;https://flink-learning.org.cn/ 目录 Flink在批处…

云效/git 删除特殊字符远程分支

云效/git 删除特殊字符远程分支 一、查看所有分支二、删除分支三、验证 在使用云效时&#xff0c;不小心添加了一个错误分支 de’vdev &#xff0c;在云效手动删除时&#xff0c;报错“找不到分支”&#xff0c;无法删除。只能启动git命令进行查看&#xff0c;将步骤总结如下&a…

【JAVA】#详细介绍!!! 文件操作之文件内容操作(2)!

本文主要是针对文件内容的操作进行展开&#xff0c;文件内容操作无非就两种 1.针对文件进行“读” 2.针对文件进行“写” 目录 文件内容读写的形式 字符流 字节流 文件内容操作 InputStream&#xff1a;以字节流的形式进行读操作 创建方式&#xff1a; FileInputStream的…

2023年深圳CPDA数据分析师认证到这里就对了哦

CPDA数据分析师认证是大数据方面的认证&#xff0c;助力数据分析人员打下扎实的数据分析基础知识功底&#xff0c;为入门数据分析保驾护航。 帮助数据分析人员掌握系统化的数据分析思维和方法论&#xff0c;提升工作效率和决策能力&#xff0c;遇到问题能够举一反三&#xff0c…

MySQL索引的底层实现原理

索引的底层实现原理 数据库索引是存储在磁盘上的&#xff0c;当数据量大时&#xff0c;就不能把整个索引全部加载到内存了&#xff0c;只能逐一加载每一个磁盘块&#xff08;对应索引树的节点&#xff09;&#xff0c;索引树越低&#xff0c;越“矮胖”&#xff0c;磁盘IO次数…

主动式和被动式电容笔的区别在哪?苹果平替笔性价比高的

被动式电容笔与主动式电容笔最大的不同之处在于主动式电容笔具有更加广泛的应用领域&#xff0c;可以与各种种类的电容式屏幕相匹配。随着对电容笔的了解&#xff0c;电容笔的使用也日益广泛。而且平替电容笔的制造工艺已经日趋成熟&#xff0c;正在走向实用&#xff0c;并且已…

易观千帆 | Q1运营报告:手机银行MAU超5.3亿,行业“内卷”超出想象

易观&#xff1a;由中国电子银行网、易观分析联合发布的“2023中国手机银行综合运营报告”显示&#xff1a;在经济企稳回升的大背景下&#xff0c;中国手机银行第一季度综合运营指数季度内呈平稳上升态势&#xff0c;手机银行活跃人数环比增幅逐月递增&#xff0c;促使活跃用户…

Redis主从复制和哨兵模式

Redis主从复制 概念 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Reds服务器。前者称为主节点(master / leader),后者称为从节点(slave / follower)。 数据的复制是单向的&#xff0c;只能由主节点到从节点。 Master以写为主&#xff0c;Slave…

无公网IP,SSH远程连接Linux CentOS服务器【内网穿透】

目录 视频教程 1. Linux CentOS安装cpolar 2. 创建TCP隧道 3. 随机地址公网远程连接 4. 固定TCP地址 5. 使用固定公网TCP地址SSH远程 本次教程我们来实现如何在外公网环境下&#xff0c;SSH远程连接家里/公司的Linux CentOS服务器&#xff0c;无需公网IP&#xff0c;也不…

Go语言的基础语法以及变量和常量

目录 基础语法 行分隔符 注释 标识符 变量 声明 赋值 作用域 常量 声明 iota 基础语法 行分隔符 在Go程序中&#xff0c;一般一行就是一个语句&#xff0c;不像Java等可以在一行写多个语句一样&#xff0c;而且最后也不需要用";"来结尾。 例如&#xf…

【华为OD机试 2023最新 】箱子之字形摆放(C语言题解 100%)

文章目录 题目描述输入描述输出描述备注用例题目解析C语言题目描述 有一批箱子(形式为字符串,设为str), 要求将这批箱子按从上到下以之字形的顺序摆放在宽度为 n 的空地,请输出箱子的摆放位置。 例如:箱子ABCDEFG,空地宽度为3,摆放结果如图: 则输出结果为: AFG BE C…

Linux Shell编程入门到实战(六)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

国考省考结构化面试:整体介绍,考试题型,考试流程,仪表着装,如何备考?

国考省考结构化面试&#xff1a;整体介绍&#xff0c;考试题型&#xff0c;考试流程&#xff0c;仪表着装&#xff0c;如何备考&#xff1f; 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重…

基于ChatGPT的文档知识库客服系统-支持上传网址/文本/docx等数据

现在&#xff0c;很多公司都有自己的内容知识库&#xff0c;会产生大量的碎片话的内部知识&#xff0c;但是这样内部知识难以整合搜索。 我开发的文档知识库客服系统 gofly.v1kf.com &#xff0c;可以应用于企业内部知识库管理&#xff0c;用户可以使用自然语言提问&#xff0c…

杂谈:铜钱儿

我个人是比较喜欢铜钱儿的。 收藏其实谈不上&#xff0c;因为我不买什么名品&#xff0c;都是玩儿一些屌丝钱&#xff0c;穷嘛&#xff0c;这个也没啥好掩饰的~ 瞎聊点儿钱币的话题吧。 小时候是家里偶尔能发现铜钱儿&#xff0c;一般都是清朝的&#xff0c;乾隆居多。有时候地…

【苹果IM群发家庭推日历推群发】筛选“兼容性”,默认为高效,挑选“兼容性”视频和图象不操纵HEVC的新格式,承袭使用旧的MPEG格式

推荐内容IMESSGAE相关 作者✈️IMEAE推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容3.日历推 *** …

Vue学习笔记(0504)

此页面对应着创建的Vue项目的显示页面 默认可以从下面的地址进行访问&#xff1a;http://localhost:8080 这里由于创建项目时我们选择了语法规范&#xff0c;所以我们在保存时哪些不符合代码规范的地方就会报错&#xff0c;只有修正代码规范后错误才会消失。 这里可以看出我们…