|
| 1 | +## [731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/) |
| 2 | + |
| 3 | +- 标签:设计、线段树、有序集合 |
| 4 | +- 难度:中等 |
| 5 | + |
| 6 | +## 题目大意 |
| 7 | + |
| 8 | +**要求**:实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。 |
| 9 | + |
| 10 | +日程可以用一对整数 $start$ 和 $end$ 表示,这里的时间是半开区间,即 $[start, end)$,实数 $x$ 的范围为 $start \le x < end$。 |
| 11 | + |
| 12 | +`MyCalendar` 类: |
| 13 | + |
| 14 | +- `MyCalendar()` 初始化日历对象。 |
| 15 | +- `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 `True` 。否则,返回 `False` 并且不要将该日程安排添加到日历中。 |
| 16 | + |
| 17 | +**说明**: |
| 18 | + |
| 19 | +- 三重预定:当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订 。 |
| 20 | +- $0 \le start < end \le 10^9$ |
| 21 | +- 每个测试用例,调用 `book` 方法的次数最多不超过 `1000` 次。 |
| 22 | + |
| 23 | +**示例**: |
| 24 | + |
| 25 | +```Python |
| 26 | +输入: |
| 27 | +["MyCalendar", "book", "book", "book"] |
| 28 | +[[], [10, 20], [15, 25], [20, 30]] |
| 29 | + |
| 30 | +输出: |
| 31 | +[null, true, false, true] |
| 32 | + |
| 33 | +解释: |
| 34 | +MyCalendar myCalendar = new MyCalendar(); |
| 35 | +myCalendar.book(10, 20); // return True |
| 36 | +myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。 |
| 37 | +myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。 |
| 38 | +``` |
| 39 | + |
| 40 | +## 解题思路 |
| 41 | + |
| 42 | +### 思路 1:线段树 |
| 43 | + |
| 44 | +这道题可以使用线段树来做。 |
| 45 | + |
| 46 | +因为区间的范围是 $[0, 10^9]$,普通数组构成的线段树不满足要求。需要用到动态开点线段树。 |
| 47 | + |
| 48 | +- 构建一棵线段树。每个线段树的节点类存储当前区间中保存的日程区间个数。 |
| 49 | + |
| 50 | +- 在 `book` 方法中,从线段树中查询 `[start, end - 1]` 区间上保存的日程区间个数。 |
| 51 | + - 如果日程区间个数大于等于 `2`,则说明该日程添加到日历中会导致三重预订,则直接返回 `False`。 |
| 52 | + - 如果日程区间个数小于 `2`,则说明该日程添加到日历中不会导致三重预订,则在线段树中将区间 `[start, end - 1]` 的日程区间个数 + 1,然后返回 `True`。 |
| 53 | + |
| 54 | +### 思路 1:线段树代码 |
| 55 | + |
| 56 | +```Python |
| 57 | +# 线段树的节点类 |
| 58 | +class SegTreeNode: |
| 59 | + def __init__(self, left=-1, right=-1, val=0, lazy_tag=None, leftNode=None, rightNode=None): |
| 60 | + self.left = left # 区间左边界 |
| 61 | + self.right = right # 区间右边界 |
| 62 | + self.mid = left + (right - left) // 2 |
| 63 | + self.leftNode = leftNode # 区间左节点 |
| 64 | + self.rightNode = rightNode # 区间右节点 |
| 65 | + self.val = val # 节点值(区间值) |
| 66 | + self.lazy_tag = lazy_tag # 区间问题的延迟更新标记 |
| 67 | + |
| 68 | + |
| 69 | +# 线段树类 |
| 70 | +class SegmentTree: |
| 71 | + # 初始化线段树接口 |
| 72 | + def __init__(self, function): |
| 73 | + self.tree = SegTreeNode(0, int(1e9)) |
| 74 | + self.function = function # function 是一个函数,左右区间的聚合方法 |
| 75 | + |
| 76 | + # 单点更新,将 nums[i] 更改为 val |
| 77 | + def update_point(self, i, val): |
| 78 | + self.__update_point(i, val, self.tree) |
| 79 | + |
| 80 | + # 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val |
| 81 | + def update_interval(self, q_left, q_right, val): |
| 82 | + self.__update_interval(q_left, q_right, val, self.tree) |
| 83 | + |
| 84 | + # 区间查询,查询区间为 [q_left, q_right] 的区间值 |
| 85 | + def query_interval(self, q_left, q_right): |
| 86 | + return self.__query_interval(q_left, q_right, self.tree) |
| 87 | + |
| 88 | + # 获取 nums 数组接口:返回 nums 数组 |
| 89 | + def get_nums(self, length): |
| 90 | + nums = [0 for _ in range(length)] |
| 91 | + for i in range(length): |
| 92 | + nums[i] = self.query_interval(i, i) |
| 93 | + return nums |
| 94 | + |
| 95 | + |
| 96 | + # 以下为内部实现方法 |
| 97 | + |
| 98 | + # 单点更新,将 nums[i] 更改为 val。node 节点的区间为 [node.left, node.right] |
| 99 | + def __update_point(self, i, val, node): |
| 100 | + if node.left == node.right: |
| 101 | + node.val = val # 叶子节点,节点值修改为 val |
| 102 | + return |
| 103 | + |
| 104 | + if i <= node.mid: # 在左子树中更新节点值 |
| 105 | + self.__update_point(i, val, node.leftNode) |
| 106 | + else: # 在右子树中更新节点值 |
| 107 | + self.__update_point(i, val, node.rightNode) |
| 108 | + self.__pushup(node) # 向上更新节点的区间值 |
| 109 | + |
| 110 | + # 区间更新 |
| 111 | + def __update_interval(self, q_left, q_right, val, node): |
| 112 | + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 |
| 113 | + if node.lazy_tag is not None: |
| 114 | + node.lazy_tag += val # 将当前节点的延迟标记增加 val |
| 115 | + else: |
| 116 | + node.lazy_tag = val # 将当前节点的延迟标记增加 val |
| 117 | + node.val += val # 当前节点所在区间增加 val |
| 118 | + return |
| 119 | + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 |
| 120 | + return 0 |
| 121 | + |
| 122 | + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 |
| 123 | + |
| 124 | + if q_left <= node.mid: # 在左子树中更新区间值 |
| 125 | + self.__update_interval(q_left, q_right, val, node.leftNode) |
| 126 | + if q_right > node.mid: # 在右子树中更新区间值 |
| 127 | + self.__update_interval(q_left, q_right, val, node.rightNode) |
| 128 | + |
| 129 | + self.__pushup(node) |
| 130 | + |
| 131 | + # 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 |
| 132 | + def __query_interval(self, q_left, q_right, node): |
| 133 | + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 |
| 134 | + return node.val # 直接返回节点值 |
| 135 | + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 |
| 136 | + return 0 |
| 137 | + |
| 138 | + self.__pushdown(node) # 向下更新节点所在区间的左右子节点的值和懒惰标记 |
| 139 | + |
| 140 | + res_left = 0 # 左子树查询结果 |
| 141 | + res_right = 0 # 右子树查询结果 |
| 142 | + if q_left <= node.mid: # 在左子树中查询 |
| 143 | + res_left = self.__query_interval(q_left, q_right, node.leftNode) |
| 144 | + if q_right > node.mid: # 在右子树中查询 |
| 145 | + res_right = self.__query_interval(q_left, q_right, node.rightNode) |
| 146 | + return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果 |
| 147 | + |
| 148 | + # 向上更新 node 节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果 |
| 149 | + def __pushup(self, node): |
| 150 | + if node.leftNode and node.rightNode: |
| 151 | + node.val = self.function(node.leftNode.val, node.rightNode.val) |
| 152 | + |
| 153 | + # 向下更新 node 节点所在区间的左右子节点的值和懒惰标记 |
| 154 | + def __pushdown(self, node): |
| 155 | + if node.leftNode is None: |
| 156 | + node.leftNode = SegTreeNode(node.left, node.mid) |
| 157 | + if node.rightNode is None: |
| 158 | + node.rightNode = SegTreeNode(node.mid + 1, node.right) |
| 159 | + |
| 160 | + lazy_tag = node.lazy_tag |
| 161 | + if node.lazy_tag is None: |
| 162 | + return |
| 163 | + |
| 164 | + if node.leftNode.lazy_tag is not None: |
| 165 | + node.leftNode.lazy_tag += lazy_tag # 更新左子节点懒惰标记 |
| 166 | + else: |
| 167 | + node.leftNode.lazy_tag = lazy_tag # 更新左子节点懒惰标记 |
| 168 | + node.leftNode.val += lazy_tag # 左子节点区间增加 lazy_tag |
| 169 | + |
| 170 | + if node.rightNode.lazy_tag is not None: |
| 171 | + node.rightNode.lazy_tag += lazy_tag # 更新右子节点懒惰标记 |
| 172 | + else: |
| 173 | + node.rightNode.lazy_tag = lazy_tag # 更新右子节点懒惰标记 |
| 174 | + node.rightNode.val += lazy_tag # 右子节点区间增加 lazy_tag |
| 175 | + |
| 176 | + node.lazy_tag = None # 更新当前节点的懒惰标记 |
| 177 | + |
| 178 | +class MyCalendarTwo: |
| 179 | + |
| 180 | + def __init__(self): |
| 181 | + self.STree = SegmentTree(lambda x, y: max(x, y)) |
| 182 | + |
| 183 | + |
| 184 | + def book(self, start: int, end: int) -> bool: |
| 185 | + if self.STree.query_interval(start, end - 1) >= 2: |
| 186 | + return False |
| 187 | + self.STree.update_interval(start, end - 1, 1) |
| 188 | + return True |
| 189 | +``` |
0 commit comments