每日一题729. 我的日程安排表 I

Posted 王六六的IT日常

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一题729. 我的日程安排表 I相关的知识,希望对你有一定的参考价值。

729. 我的日程安排表 I


参考:大佬在力扣题解写的博文-----线段树详解「汇总级别整理 🔥🔥🔥」
线段树系列:
线段树解决的是「区间和」的问题,且该「区间」会被修改。
举个简单的🌰,对于 nums = [1, 2, 3, 4, 5]。
如果我们需要多次求某一个区间的和,是不是首先想到了利用「前缀和」。
关于前缀和的详细介绍可见👉 前缀和数组

nums = [1, 2, 3, 4, 5] 对应的线段树如下所示:

每个节点代表一个区间,而节点的值就是该区间的和。

  • 数字之和「总数字之和 = 左区间数字之和 + 右区间数字之和」
  • 最大公因数 (GCD)「总 GCD = gcd(左区间 GCD, 右区间 GCD)」
  • 最大值「总最大值 = max(左区间最大值,右区间最大值)」

线段树的数据结构
我们可以使用数组来表示一棵线段树,假如根节点为 i,那么左孩子的节点就为 2 * i,右孩子的节点就为 2 * i + 1 (前提:i 从 1 开始)

我们可以使用链表来表示一棵线段树,其节点的数据结构如下:

class Node 
    // 左右孩子节点
    Node left, right;
    // 当前节点值
    int val;

比较倾向使用链表,因为比较节约内存,下面的实现均基于链表。

线段树的建立

如果题目中给了具体的区间范围,我们根据该范围建立线段树。

public void buildTree(Node node, int start, int end) 
    // 到达叶子节点
    if (start == end) 
        node.val = arr[start];
        return ;
    
    int mid = (start + end) >> 1;
    buildTree(node.left, start, mid);
    buildTree(node.right, mid + 1, end);
    // 向上更新
    pushUp(node);

// 向上更新
private void pushUp(Node node) 
    node.val = node.left.val + node.right.val;

但是很多时候,题目中都没有给出很具体的范围,只有数据的取值范围,一般都很大,所以我们更常用的是「动态开点」。
「动态开点」一般是在「更新」或「查询」的时候动态的建立节点,具体可见下面的更新和查询操作。

线段树完整模版

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

/**
 * @Description: 线段树(动态开点)
 * @Author: LFool
 * @Date 2022/6/7 09:15
 **/
public class SegmentTreeDynamic 
    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 += (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;
    

class MyCalendar 

    public MyCalendar() 

    
    
    public boolean book(int start, int end) 
        // 先查询该区间是否为 0
        if (query(root, 0, N, start, end - 1) != 0) 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 && 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;
    



/**
 * Your MyCalendar object will be instantiated and called as such:
 * MyCalendar obj = new MyCalendar();
 * boolean param_1 = obj.book(start,end);
 */

以上是关于每日一题729. 我的日程安排表 I的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 729. My Calendar I | 729. 我的日程安排表 I(线段树)

每日一题731. 我的日程安排表Ⅱ

729. 我的日程安排表 I(set or 动态开点线段树)

c++线段树

leetcode 每日一题 15. 三数之和

leetcode 每日一题 18. 四数之和