Skip to content

Commit 4cca60b

Browse files
author
杨世超
committed
Create 0731. 我的日程安排表 II.md
1 parent d58ba3f commit 4cca60b

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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

Comments
 (0)