Skip to content

Commit 44cac33

Browse files
committed
✨feat: Add 713 & 729
1 parent 1755697 commit 44cac33

File tree

3 files changed

+252
-2
lines changed

3 files changed

+252
-2
lines changed

LeetCode/381-390/390. 消除游戏(中等).md

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ Tag : 「动态规划」、「数学」、「约瑟夫环」
66

77

88

9-
109
列表 `arr` 由在范围 `[1, n]` 中的所有整数组成,并按严格递增排序。
1110

1211
请你对 `arr` 应用下述算法:
@@ -45,7 +44,7 @@ arr = [6]
4544

4645
与求解约瑟夫环类似,本题也可以通常找规律,分析出公式之后进行递推求解。
4746

48-
对于本题,定义 $f[i]$ 为在 **连续序列** $[1, i]$ 中进行「起始从左到右」的轮流换向间隔删除,最终左边剩余的编号;定义 $f'[i]$ 为在 **连续序列** $[1, i]$ 中进行「起始从右到左」的轮流换向间隔删除,最终右边剩余的编号。
47+
对于本题,定义 $f[i]$ 为在 **连续序列** $[1, i]$ 中进行「起始先从左到右,再从右往左」的轮流换向间隔删除,最终左边剩余的编号;定义 $f'[i]$ 为在 **连续序列** $[1, i]$ 中进行「起始先从右到左,再从左往右」的轮流换向间隔删除,最终右边剩余的编号。
4948

5049
**由于「从左往右」和「从右往左」分别为「从左端点发起,间隔删除」和「从右端点发起,间隔删除」,因此整个删除过程在连续序列中 $[1, i]$ 中具有对称性,两者最终剩余的编号在连续序列中也具有对称性。**
5150

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[713. 乘积小于 K 的子数组]()** ,难度为 **中等**
4+
5+
Tag : 「滑动窗口」、「双指针」
6+
7+
8+
9+
给你一个整数数组 `nums` 和一个整数 $k$ ,请你返回子数组内所有元素的乘积严格小于 $k$ 的连续子数组的数目。
10+
11+
示例 1:
12+
```
13+
输入:nums = [10,5,2,6], k = 100
14+
15+
输出:8
16+
17+
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
18+
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
19+
```
20+
示例 2:
21+
```
22+
输入:nums = [1,2,3], k = 0
23+
24+
输出:0
25+
```
26+
27+
提示: 
28+
* $1 <= nums.length <= 3 * 10^4$
29+
* $1 <= nums[i] <= 1000$
30+
* $0 <= k <= 10^6$
31+
32+
---
33+
34+
### 滑动窗口
35+
36+
利用 $1 <= nums[i] <= 1000$,我们可以从前往后处理所有的 $nums[i]$,使用一个变量 $cur$ 记录当前窗口的乘积,使用两个变量 $j$ 和 $i$ 分别代表当前窗口的左右端点。
37+
38+
当 $cur >= k$ 时,我们考虑将左端点 $j$ 右移,同时消除原来左端点元素 $nums[j]$ 对 $cur$ 的贡献,直到 $cur >= k$ 不再满足,这样我们就可以得到每个右端点 $nums[i]$ 的最远左端点 $nums[j]$,从而得知以 $nums[i]$ 为结尾的合法子数组个数为 $i - j + 1$。
39+
40+
代码:
41+
```Java
42+
class Solution {
43+
public int numSubarrayProductLessThanK(int[] nums, int k) {
44+
int n = nums.length, ans = 0;
45+
if (k <= 1) return 0;
46+
for (int i = 0, j = 0, cur = 1; i < n; i++) {
47+
cur *= nums[i];
48+
while (cur >= k) cur /= nums[j++];
49+
ans += i - j + 1;
50+
}
51+
return ans;
52+
}
53+
}
54+
```
55+
* 时间复杂度:$O(n)$
56+
* 空间复杂度:$O(1)$
57+
58+
---
59+
60+
### 最后
61+
62+
这是我们「刷穿 LeetCode」系列文章的第 `No.713` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
63+
64+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
65+
66+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
67+
68+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
69+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[729. 我的日程安排表 I]()** ,难度为 **中等**
4+
5+
Tag : 「模拟」、「红黑树」、「线段树」
6+
7+
8+
9+
实现一个 `MyCalendar` 类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ,则可以存储这个新的日程安排。
10+
11+
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生 重复预订 。
12+
13+
日程可以用一对整数 `start``end` 表示,这里的时间是半开区间,即 $[start, end)$, 实数 $x$ 的范围为,  $start <= x < end$ 。
14+
15+
实现 MyCalendar 类:
16+
17+
* `MyCalendar()` 初始化日历对象。
18+
* `boolean book(int start, int end)` 如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 `true`。否则,返回 `false` 并且不要将该日程安排添加到日历中。
19+
20+
示例:
21+
```
22+
输入:
23+
["MyCalendar", "book", "book", "book"]
24+
[[], [10, 20], [15, 25], [20, 30]]
25+
26+
输出:
27+
[null, true, false, true]
28+
29+
解释:
30+
MyCalendar myCalendar = new MyCalendar();
31+
myCalendar.book(10, 20); // return True
32+
myCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。
33+
myCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。
34+
```
35+
36+
提示:
37+
* $0 <= start < end <= 10^9$
38+
* 每个测试用例,调用 `book` 方法的次数最多不超过 $1000$ 次。
39+
40+
---
41+
42+
### 模拟
43+
44+
利用 `book` 操作最多调用 $1000$ 次,我们可以使用一个数组存储所有已被预定的日期 $[start, end - 1]$,对于每次 `book` 操作,检查当前传入的 $[start, end)$ 是否会与已有的日期冲突,冲突返回 `False`,否则将 $[start, end- 1]$ 插入数组并返回 `True`
45+
46+
代码:
47+
```Java
48+
class MyCalendar {
49+
List<int[]> list = new ArrayList<>();
50+
public boolean book(int start, int end) {
51+
end--;
52+
for (int[] info : list) {
53+
int l = info[0], r = info[1];
54+
if (start > r || end < l) continue;
55+
return false;
56+
}
57+
list.add(new int[]{start, end});
58+
return true;
59+
}
60+
}
61+
```
62+
* 时间复杂度:令 $n$ 为 `book` 的最大调用次数,复杂度为 $O(n^2)$
63+
* 空间复杂度:$O(n)$
64+
65+
---
66+
67+
### 红黑树
68+
69+
解法一中,每次的 `book` 操作我们都不可避免的需要遍历所有已存在的日期。
70+
71+
实际上,如果我们使用 `TreeMap`(底层为红黑树)来维护所有日期,可以避免对所有已存在的日期进行遍历。
72+
73+
74+
代码:
75+
```Java
76+
class MyCalendar {
77+
TreeMap<Integer, Integer> tm = new TreeMap<>();
78+
public boolean book(int start, int end) {
79+
Integer prev = tm.floorKey(start), next = tm.ceilingKey(start);
80+
if ((prev == null || tm.get(prev) <= start) && (next == null || end <= next)) {
81+
tm.put(start, end);
82+
return true;
83+
}
84+
return false;
85+
}
86+
}
87+
```
88+
* 时间复杂度:令 $n$ 为 `book` 的最大调用次数,复杂度为 $O(n * \log{n})$
89+
* 空间复杂度:$O(n)$
90+
91+
---
92+
93+
### 线段树(动态开点)
94+
95+
对于常规的线段树实现来说,都是一开始就调用 `build` 操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始 `build` 空树的时候已经锁死。
96+
97+
如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决 `MLE` 问题。
98+
99+
但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
100+
101+
动态开点的优势在于,不需要事前构造空树,而是在插入操作 `add` 和查询操作 `query` 时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过 `u << 1``u << 1 | 1` 的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在 `tr` 数组的下标进行存储,分别记为 `ls``rs` 属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。
102+
103+
由于存在「懒标记」,线段树的插入和查询都是 $\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\log{n}$ 的点,因此空间复杂度为 $O(m\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\log{n}$ 来进行,还要对常熟进行分析,才能得到准确的点数上界。
104+
105+
动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
106+
107+
当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开( `Java` 的 $128M$ 可以开到 $5 * 10^6$ 以上)。
108+
109+
代码:
110+
```Java
111+
class MyCalendar {
112+
class Node {
113+
// ls 和 rs 分别代表当前节点的左右子节点在 tr 的下标
114+
// val 代表当前节点有多少数
115+
// add 为懒标记
116+
int ls, rs, add, val;
117+
}
118+
int N = (int)1e9, M = 120010, cnt = 1;
119+
Node[] tr = new Node[M];
120+
void update(int u, int lc, int rc, int l, int r, int v) {
121+
if (l <= lc && rc <= r) {
122+
tr[u].val += (rc - lc + 1) * v;
123+
tr[u].add += v;
124+
return ;
125+
}
126+
lazyCreate(u);
127+
pushdown(u, rc - lc + 1);
128+
int mid = lc + rc >> 1;
129+
if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);
130+
if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);
131+
pushup(u);
132+
}
133+
int query(int u, int lc, int rc, int l, int r) {
134+
if (l <= lc && rc <= r) return tr[u].val;
135+
lazyCreate(u);
136+
pushdown(u, rc - lc + 1);
137+
int mid = lc + rc >> 1, ans = 0;
138+
if (l <= mid) ans = query(tr[u].ls, lc, mid, l, r);
139+
if (r > mid) ans += query(tr[u].rs, mid + 1, rc, l, r);
140+
return ans;
141+
}
142+
void lazyCreate(int u) {
143+
if (tr[u] == null) tr[u] = new Node();
144+
if (tr[u].ls == 0) {
145+
tr[u].ls = ++cnt;
146+
tr[tr[u].ls] = new Node();
147+
}
148+
if (tr[u].rs == 0) {
149+
tr[u].rs = ++cnt;
150+
tr[tr[u].rs] = new Node();
151+
}
152+
}
153+
void pushdown(int u, int len) {
154+
tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;
155+
tr[tr[u].ls].val += len / 2 * tr[u].add; tr[tr[u].rs].val += (len - len / 2) * tr[u].add;
156+
tr[u].add = 0;
157+
}
158+
void pushup(int u) {
159+
tr[u].val = tr[tr[u].ls].val + tr[tr[u].rs].val;
160+
}
161+
public boolean book(int start, int end) {
162+
if (query(1, 1, N + 1, start + 1, end) > 0) return false;
163+
update(1, 1, N + 1, start + 1, end, 1);
164+
return true;
165+
}
166+
}
167+
```
168+
* 时间复杂度:令 $n$ 为值域大小,本题固定为 $1e9$,线段树的查询和增加复杂度均为 $O(\log{n})$
169+
* 空间复杂度:令询问数量为 $m$,复杂度为 $O(m\log{n})$
170+
171+
---
172+
173+
### 最后
174+
175+
这是我们「刷穿 LeetCode」系列文章的第 `No.729` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
176+
177+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
178+
179+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
180+
181+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
182+

0 commit comments

Comments
 (0)