Skip to content

Commit f833be8

Browse files
committed
✨feat: Add 731、732
1 parent e89534a commit f833be8

4 files changed

+279
-1
lines changed

Index/线段树.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [307. 区域和检索 - 数组可修改](https://leetcode-cn.com/problems/range-sum-query-mutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-mutable/solution/by-ac_oier-zmbn/) | 中等 | 🤩🤩🤩🤩🤩 |
44
| [729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/) | 中等 | 🤩🤩🤩🤩🤩 |
5+
| [731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/) | 中等 | 🤩🤩🤩🤩🤩 |
6+
| [732. 我的日程安排表 III](https://leetcode-cn.com/problems/my-calendar-iii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-iii/solution/by-ac_oier-cv31/) | 困难 | 🤩🤩🤩🤩🤩 |
57
| [1109. 航班预订统计](https://leetcode-cn.com/problems/corporate-flight-bookings/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/corporate-flight-bookings/solution/gong-shui-san-xie-yi-ti-shuang-jie-chai-fm1ef/) | 中等 | 🤩🤩🤩🤩🤩 |
68
| [1893. 检查是否区域内所有整数都被覆盖](https://leetcode-cn.com/problems/check-if-all-the-integers-in-a-range-are-covered/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/check-if-all-the-integers-in-a-range-are-covered/solution/gong-shui-san-xie-yi-ti-shuang-jie-mo-ni-j83x/) | 简单 | 🤩🤩🤩🤩 |
79
| [2213. 由单个字符重复的最长子字符串](https://leetcode-cn.com/problems/longest-substring-of-one-repeating-character/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/longest-substring-of-one-repeating-character/solution/by-ac_oier-0lso/) | 困难 | 🤩🤩🤩🤩🤩 |

LeetCode/711-720/713. 乘积小于 K 的子数组(中等).md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### 题目描述
22

3-
这是 LeetCode 上的 **[713. 乘积小于 K 的子数组]()** ,难度为 **中等**
3+
这是 LeetCode 上的 **[713. 乘积小于 K 的子数组](https://leetcode-cn.com/problems/subarray-product-less-than-k/solution/by-ac_oier-3w08/)** ,难度为 **中等**
44

55
Tag : 「滑动窗口」、「双指针」
66

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/)** ,难度为 **中等**
4+
5+
Tag : 「线段树」
6+
7+
8+
9+
实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。
10+
11+
`MyCalendar` 有一个 `book(int start, int end)` 方法。它意味着在 `start``end` 时间内增加一个日程安排,注意,这里的时间是半开区间,即 $[start, end)$, 实数 $x$ 的范围为,$ start <= x < end$。
12+
13+
当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。
14+
15+
每次调用 `MyCalendar.book` 方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 `true`。否则,返回 `false` 并且不要将该日程安排添加到日历中。
16+
17+
请按照以下步骤调用 `MyCalendar` 类: `MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)`
18+
19+
示例:
20+
```
21+
MyCalendar();
22+
MyCalendar.book(10, 20); // returns true
23+
MyCalendar.book(50, 60); // returns true
24+
MyCalendar.book(10, 40); // returns true
25+
MyCalendar.book(5, 15); // returns false
26+
MyCalendar.book(5, 10); // returns true
27+
MyCalendar.book(25, 55); // returns true
28+
29+
解释:
30+
前两个日程安排可以添加至日历中。 第三个日程安排会导致双重预订,但可以添加至日历中。
31+
第四个日程安排活动(5,15)不能添加至日历中,因为它会导致三重预订。
32+
第五个日程安排(5,10)可以添加至日历中,因为它未使用已经双重预订的时间10。
33+
第六个日程安排(25,55)可以添加至日历中,因为时间 [25,40] 将和第三个日程安排双重预订;
34+
时间 [40,50] 将单独预订,时间 [50,55)将和第二个日程安排双重预订。
35+
```
36+
37+
提示:
38+
* 每个测试用例,调用 `MyCalendar.book` 函数最多不超过 $1000$ 次。
39+
* 调用函数 `MyCalendar.book(start, end)` 时, `start` 和 `end` 的取值范围为 $[0, 10^9]$。
40+
41+
---
42+
43+
### 线段树(动态开点)
44+
45+
[729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/) 几乎完全一致,只需要将对「线段树」所维护的节点信息进行调整即可。
46+
47+
线段树维护的节点信息包括:
48+
49+
* `ls/rs`: 分别代表当前节点的左右子节点在线段树数组 `tr` 中的下标;
50+
* `add`: 懒标记;
51+
* `max`: 为当前区间的最大值。
52+
53+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
54+
55+
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
56+
57+
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
58+
59+
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `add` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
60+
61+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常熟进行分析,才能得到准确的点数上界。
62+
63+
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
64+
65+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
66+
67+
代码:
68+
```Java
69+
class MyCalendarTwo {
70+
class Node {
71+
// ls 和 rs 分别代表当前节点的左右子节点在 tr 的下标
72+
// add 为懒标记
73+
// max 为当前区间的最大值
74+
int ls, rs, add, max;
75+
}
76+
int N = (int)1e9, M = 1000010, cnt = 1;
77+
Node[] tr = new Node[M];
78+
void update(int u, int lc, int rc, int l, int r, int v) {
79+
if (l <= lc && rc <= r) {
80+
tr[u].add += v;
81+
tr[u].max += v;
82+
return ;
83+
}
84+
lazyCreate(u);
85+
pushdown(u);
86+
int mid = lc + rc >> 1;
87+
if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);
88+
if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);
89+
pushup(u);
90+
}
91+
int query(int u, int lc, int rc, int l, int r) {
92+
if (l <= lc && rc <= r) return tr[u].max;
93+
lazyCreate(u);
94+
pushdown(u);
95+
int mid = lc + rc >> 1, ans = 0;
96+
if (l <= mid) ans = Math.max(ans, query(tr[u].ls, lc, mid, l, r));
97+
if (r > mid) ans = Math.max(ans, query(tr[u].rs, mid + 1, rc, l, r));
98+
return ans;
99+
}
100+
void lazyCreate(int u) {
101+
if (tr[u] == null) tr[u] = new Node();
102+
if (tr[u].ls == 0) {
103+
tr[u].ls = ++cnt;
104+
tr[tr[u].ls] = new Node();
105+
}
106+
if (tr[u].rs == 0) {
107+
tr[u].rs = ++cnt;
108+
tr[tr[u].rs] = new Node();
109+
}
110+
}
111+
void pushup(int u) {
112+
tr[u].max = Math.max(tr[tr[u].ls].max, tr[tr[u].rs].max);
113+
}
114+
void pushdown(int u) {
115+
tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;
116+
tr[tr[u].ls].max += tr[u].add; tr[tr[u].rs].max += tr[u].add;
117+
tr[u].add = 0;
118+
}
119+
public boolean book(int start, int end) {
120+
if (query(1, 1, N + 1, start + 1, end) >= 2) return false;
121+
update(1, 1, N + 1, start + 1, end, 1);
122+
return true;
123+
}
124+
}
125+
```
126+
* 时间复杂度:令 $n$ 为值域大小,本题固定为 $1e9$,线段树的查询和增加复杂度均为 $O(\log{n})$
127+
* 空间复杂度:令询问数量为 $m$,复杂度为 $O(m\log{n})$
128+
129+
---
130+
131+
### 最后
132+
133+
这是我们「刷穿 LeetCode」系列文章的第 `No.731` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
134+
135+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
136+
137+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
138+
139+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
140+
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[732. 我的日程安排表 III](https://leetcode-cn.com/problems/my-calendar-iii/solution/by-ac_oier-cv31/)** ,难度为 **困难**
4+
5+
Tag : 「线段树」
6+
7+
8+
9+
当 $k$ 个日程安排有一些时间上的交叉时(例如 $k$ 个日程安排都在同一时间内),就会产生 $k$ 次预订。
10+
11+
给你一些日程安排 $[start, end)$ ,请你在每个日程安排添加后,返回一个整数 $k$ ,表示所有先前日程安排会产生的最大 $k$ 次预订。
12+
13+
实现一个 `MyCalendarThree` 类来存放你的日程安排,你可以一直添加新的日程安排。
14+
15+
* `MyCalendarThree()` 初始化对象。
16+
* `int book(int start, int end)` 返回一个整数 $k$ ,表示日历中存在的 $k$ 次预订的最大值。
17+
18+
示例:
19+
```
20+
输入:
21+
["MyCalendarThree", "book", "book", "book", "book", "book", "book"]
22+
[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]
23+
24+
输出:
25+
[null, 1, 1, 2, 3, 3, 3]
26+
27+
解释:
28+
MyCalendarThree myCalendarThree = new MyCalendarThree();
29+
myCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
30+
myCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
31+
myCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。
32+
myCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。
33+
myCalendarThree.book(5, 10); // 返回 3
34+
myCalendarThree.book(25, 55); // 返回 3
35+
```
36+
37+
提示:
38+
* $0 <= start < end <= 10^9$
39+
* 每个测试用例,调用 `book` 函数最多不超过 $400$ 次
40+
41+
---
42+
43+
### 线段树(动态开点)
44+
45+
[731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/) 几乎完全一致,只需要将对「线段树」所维护的节点信息进行调整即可。
46+
47+
线段树维护的节点信息包括:
48+
49+
* `ls/rs`: 分别代表当前节点的左右子节点在线段树数组 `tr` 中的下标;
50+
* `add`: 懒标记;
51+
* `max`: 为当前区间的最大值。
52+
53+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
54+
55+
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
56+
57+
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
58+
59+
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `add` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
60+
61+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常熟进行分析,才能得到准确的点数上界。
62+
63+
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
64+
65+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
66+
67+
代码:
68+
```Java
69+
class MyCalendarThree {
70+
class Node {
71+
int ls, rs, add, max;
72+
}
73+
int N = (int)1e9, M = 4 * 400 * 20, cnt = 1;
74+
Node[] tr = new Node[M];
75+
void update(int u, int lc, int rc, int l, int r, int v) {
76+
if (l <= lc && rc <= r) {
77+
tr[u].add += v;
78+
tr[u].max += v;
79+
return ;
80+
}
81+
lazyCreate(u);
82+
pushdown(u);
83+
int mid = lc + rc >> 1;
84+
if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);
85+
if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);
86+
pushup(u);
87+
}
88+
int query(int u, int lc, int rc, int l, int r) {
89+
if (l <= lc && rc <= r) return tr[u].max;
90+
lazyCreate(u);
91+
pushdown(u);
92+
int mid = lc + rc >> 1, ans = 0;
93+
if (l <= mid) ans = Math.max(ans, query(tr[u].ls, lc, mid, l, r));
94+
if (r > mid) ans = Math.max(ans, query(tr[u].rs, mid + 1, rc, l, r));
95+
return ans;
96+
}
97+
void lazyCreate(int u) {
98+
if (tr[u] == null) tr[u] = new Node();
99+
if (tr[u].ls == 0) {
100+
tr[u].ls = ++cnt;
101+
tr[tr[u].ls] = new Node();
102+
}
103+
if (tr[u].rs == 0) {
104+
tr[u].rs = ++cnt;
105+
tr[tr[u].rs] = new Node();
106+
}
107+
}
108+
void pushdown(int u) {
109+
tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;
110+
tr[tr[u].ls].max += tr[u].add; tr[tr[u].rs].max += tr[u].add;
111+
tr[u].add = 0;
112+
}
113+
void pushup(int u) {
114+
tr[u].max = Math.max(tr[tr[u].ls].max, tr[tr[u].rs].max);
115+
}
116+
public int book(int start, int end) {
117+
update(1, 1, N + 1, start + 1, end, 1);
118+
return query(1, 1, N + 1, 1, N + 1);
119+
}
120+
}
121+
```
122+
* 时间复杂度:令 $n$ 为值域大小,本题固定为 $1e9$,线段树的查询和增加复杂度均为 $O(\log{n})$
123+
* 空间复杂度:令询问数量为 $m$,复杂度为 $O(m\log{n})$
124+
125+
---
126+
127+
### 最后
128+
129+
这是我们「刷穿 LeetCode」系列文章的第 `No.732` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
130+
131+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
132+
133+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
134+
135+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
136+

0 commit comments

Comments
 (0)