Skip to content

Commit d49e419

Browse files
author
杨世超
committed
Create 1450. 在既定时间做作业的学生人数.md
1 parent d8949ea commit d49e419

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
## [1450. 在既定时间做作业的学生人数](https://leetcode-cn.com/problems/number-of-students-doing-homework-at-a-given-time/)
2+
3+
- 标签:数组
4+
- 难度:简单
5+
6+
## 题目大意
7+
8+
**描述**:给你两个长度相等的整数数组,一个表示开始时间的数组 `startTime` ,另一个表示结束时间的数组 `endTime`。再给定一个整数 `queryTime` 作为查询时间。已知第 `i` 名学生在 `startTime[i]` 时开始写作业并于 `endTime[i]` 时完成作业。
9+
10+
**要求**:返回在查询时间 `queryTime` 时正在做作业的学生人数。即能够使 `queryTime` 处于区间 `[startTime[i], endTime[i]]` 的学生人数。
11+
12+
**说明**
13+
14+
- $startTime.length == endTime.length$。
15+
- $1\le startTime.length \le 100$。
16+
- $1 \le startTime[i] \le endTime[i] \le 1000$。
17+
- $1 \le queryTime \le 1000$。
18+
19+
**示例**
20+
21+
```Python
22+
输入 startTime = [4], endTime = [4], queryTime = 4
23+
输出 1
24+
解释 在查询时间只有一名学生在做作业。
25+
```
26+
27+
## 解题思路
28+
29+
### 思路 1:枚举算法
30+
31+
- 维护一个用于统计在查询时间 `queryTime` 时正在做作业的学生人数的变量 `cnt`。然后遍历所有学生的开始时间和结束时间。
32+
- 如果 `queryTime` 在区间 `[startTime[i], endTime[i]]` 之间,即 `startTime[i] <= queryTime <= endTime[i]`,则令 `cnt``1`
33+
- 遍历完输出统计人数 `cnt`
34+
35+
### 思路 1:枚举算法代码
36+
37+
```Python
38+
class Solution:
39+
def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int:
40+
cnt = 0
41+
size = len(startTime)
42+
for i in range(size):
43+
if startTime[i] <= queryTime <= endTime[i]:
44+
cnt += 1
45+
return cnt
46+
```
47+
48+
### 思路 2:线段树
49+
50+
- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 `[0, 1000]` 的线段树,初始化所有区间值都为 `0`
51+
- 然后遍历所有学生的开始时间和结束时间,并将区间 `[startTime[i], endTime[i]]` 值加 `1`
52+
- 在线段树中查询 `queryTime` 对应的单点区间 `[queryTime, queryTime]` 的最大值为多少。
53+
54+
### 思路 2:线段树代码
55+
56+
```Python
57+
class TreeNode:
58+
def __init__(self, val=0):
59+
self.left = -1 # 区间左边界
60+
self.right = -1 # 区间右边界
61+
self.val = val # 节点值(区间值)
62+
self.lazy_tag = None # 区间和问题的延迟更新标记
63+
64+
65+
# 线段树类
66+
class SegmentTree:
67+
def __init__(self, nums, function):
68+
self.size = len(nums)
69+
self.tree = [TreeNode() for _ in range(4 * self.size)] # 维护 TreeNode 数组
70+
self.nums = nums # 原始数据
71+
self.function = function # function 是一个函数,左右区间的聚合方法
72+
if self.size > 0:
73+
self.__build(0, 0, self.size - 1)
74+
75+
# 构建线段树,节点的存储下标为 index,节点的区间为 [left, right]
76+
def __build(self, index, left, right):
77+
self.tree[index].left = left
78+
self.tree[index].right = right
79+
if left == right: # 叶子节点,节点值为对应位置的元素值
80+
self.tree[index].val = self.nums[left]
81+
return
82+
83+
mid = left + (right - left) // 2 # 左右节点划分点
84+
left_index = index * 2 + 1 # 左子节点的存储下标
85+
right_index = index * 2 + 2 # 右子节点的存储下标
86+
self.__build(left_index, left, mid) # 递归创建左子树
87+
self.__build(right_index, mid + 1, right) # 递归创建右子树
88+
self.__pushup(index) # 向上更新节点的区间值
89+
90+
# 向上更新下标为 index 的节点区间值,节点的区间值等于该节点左右子节点元素值的聚合计算结果
91+
def __pushup(self, index):
92+
left_index = index * 2 + 1 # 左子节点的存储下标
93+
right_index = index * 2 + 2 # 右子节点的存储下标
94+
self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val)
95+
96+
# 单点更新,将 nums[i] 更改为 val
97+
def update_point(self, i, val):
98+
self.nums[i] = val
99+
self.__update_point(i, val, 0, 0, self.size - 1)
100+
101+
# 单点更新,将 nums[i] 更改为 val。节点的存储下标为 index,节点的区间为 [left, right]
102+
def __update_point(self, i, val, index, left, right):
103+
if self.tree[index].left == self.tree[index].right:
104+
self.tree[index].val = val # 叶子节点,节点值修改为 val
105+
return
106+
107+
mid = left + (right - left) // 2 # 左右节点划分点
108+
left_index = index * 2 + 1 # 左子节点的存储下标
109+
right_index = index * 2 + 2 # 右子节点的存储下标
110+
if i <= mid: # 在左子树中更新节点值
111+
self.__update_point(i, val, left_index, left, mid)
112+
else: # 在右子树中更新节点值
113+
self.__update_point(i, val, right_index, mid + 1, right)
114+
self.__pushup(index) # 向上更新节点的区间值
115+
116+
# 区间查询,查询区间为 [q_left, q_right] 的区间值
117+
def query_interval(self, q_left, q_right):
118+
return self.__query_interval(q_left, q_right, 0, 0, self.size - 1)
119+
120+
# 区间查询,在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值
121+
def __query_interval(self, q_left, q_right, index, left, right):
122+
if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖
123+
return self.tree[index].val # 直接返回节点值
124+
if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关
125+
return 0
126+
127+
self.__pushdown(index)
128+
129+
mid = left + (right - left) // 2 # 左右节点划分点
130+
left_index = index * 2 + 1 # 左子节点的存储下标
131+
right_index = index * 2 + 2 # 右子节点的存储下标
132+
res_left = 0 # 左子树查询结果
133+
res_right = 0 # 右子树查询结果
134+
if q_left <= mid: # 在左子树中查询
135+
res_left = self.__query_interval(q_left, q_right, left_index, left, mid)
136+
if q_right > mid: # 在右子树中查询
137+
res_right = self.__query_interval(q_left, q_right, right_index, mid + 1, right)
138+
return self.function(res_left, res_right) # 返回左右子树元素值的聚合计算结果
139+
140+
# 区间更新,将区间为 [q_left, q_right] 上的元素值修改为 val
141+
def update_interval(self, q_left, q_right, val):
142+
self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1)
143+
144+
# 区间更新
145+
def __update_interval(self, q_left, q_right, val, index, left, right):
146+
147+
if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖
148+
if self.tree[index].lazy_tag:
149+
self.tree[index].lazy_tag += val # 将当前节点的延迟标记增加 val
150+
else:
151+
self.tree[index].lazy_tag = val # 将当前节点的延迟标记增加 val
152+
interval_size = (right - left + 1) # 当前节点所在区间大小
153+
self.tree[index].val += val * interval_size # 当前节点所在区间每个元素值增加 val
154+
return
155+
if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关
156+
return 0
157+
158+
self.__pushdown(index)
159+
160+
mid = left + (right - left) // 2 # 左右节点划分点
161+
left_index = index * 2 + 1 # 左子节点的存储下标
162+
right_index = index * 2 + 2 # 右子节点的存储下标
163+
if q_left <= mid: # 在左子树中更新区间值
164+
self.__update_interval(q_left, q_right, val, left_index, left, mid)
165+
if q_right > mid: # 在右子树中更新区间值
166+
self.__update_interval(q_left, q_right, val, right_index, mid + 1, right)
167+
168+
self.__pushup(index)
169+
170+
# 向下更新下标为 index 的节点所在区间的左右子节点的值和懒惰标记
171+
def __pushdown(self, index):
172+
lazy_tag = self.tree[index].lazy_tag
173+
if not lazy_tag:
174+
return
175+
176+
left_index = index * 2 + 1 # 左子节点的存储下标
177+
right_index = index * 2 + 2 # 右子节点的存储下标
178+
179+
if self.tree[left_index].lazy_tag:
180+
self.tree[left_index].lazy_tag += lazy_tag # 更新左子节点懒惰标记
181+
else:
182+
self.tree[left_index].lazy_tag = lazy_tag
183+
left_size = (self.tree[left_index].right - self.tree[left_index].left + 1)
184+
self.tree[left_index].val += lazy_tag * left_size # 左子节点每个元素值增加 lazy_tag
185+
186+
if self.tree[right_index].lazy_tag:
187+
self.tree[right_index].lazy_tag += lazy_tag # 更新右子节点懒惰标记
188+
else:
189+
self.tree[right_index].lazy_tag = lazy_tag
190+
right_size = (self.tree[right_index].right - self.tree[right_index].left + 1)
191+
self.tree[right_index].val += lazy_tag * right_size # 右子节点每个元素值增加 lazy_tag
192+
193+
self.tree[index].lazy_tag = None # 更新当前节点的懒惰标记
194+
195+
# 获取 nums 数组
196+
def get_nums(self):
197+
for i in range(self.size):
198+
self.nums[i] = self.query_interval(i, i)
199+
return self.nums
200+
201+
class Solution:
202+
def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int:
203+
nums = [0 for _ in range(1010)]
204+
self.ST = SegmentTree(nums, lambda x, y: max(x, y))
205+
size = len(startTime)
206+
for i in range(size):
207+
self.ST.update_interval(startTime[i], endTime[i], 1)
208+
209+
return self.ST.query_interval(queryTime, queryTime)
210+
```
211+
212+
### 思路 3:树状数组
213+
214+
- 因为 $1 \le startTime[i] \le endTime[i] \le 1000$,所以我们可以维护一个区间为 `[0, 1000]` 的树状数组。
215+
- 注意:
216+
- 树状数组中 `update(self, index, delta):` 指的是将对应元素 `nums[index] ` 加上 `delta`
217+
- `query(self, index):` 指的是 `index` 位置之前的元素和,即前缀和。
218+
- 然后遍历所有学生的开始时间和结束时间,将树状数组上 `startTime[i]` 的值增加 `1`,再将树状数组上`endTime[i]` 的值减少 `1`
219+
- 则查询 `queryTime` 位置的前缀和即为答案。
220+
221+
### 思路 3:树状数组代码
222+
223+
```Python
224+
class BinaryIndexTree:
225+
226+
def __init__(self, n):
227+
self.size = n
228+
self.tree = [0 for _ in range(n + 1)]
229+
230+
def lowbit(self, index):
231+
return index & (-index)
232+
233+
def update(self, index, delta):
234+
while index <= self.size:
235+
self.tree[index] += delta
236+
index += self.lowbit(index)
237+
238+
def query(self, index):
239+
res = 0
240+
while index > 0:
241+
res += self.tree[index]
242+
index -= self.lowbit(index)
243+
return res
244+
245+
class Solution:
246+
def busyStudent(self, startTime: List[int], endTime: List[int], queryTime: int) -> int:
247+
bit = BinaryIndexTree(1010)
248+
size = len(startTime)
249+
for i in range(size):
250+
bit.update(startTime[i], 1)
251+
bit.update(endTime[i] + 1, -1)
252+
return bit.query(queryTime)
253+
```
254+

0 commit comments

Comments
 (0)