Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a96b7db

Browse files
Merge pull request SharingSource#530 from SharingSource/ac_oier
✨feat: Add 699 & 面试题 17.11
2 parents 5e07c3b + 5512c15 commit a96b7db

File tree

6 files changed

+337
-3
lines changed

6 files changed

+337
-3
lines changed

‎Index/双指针.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@
4949
| [2024. 考试的最大困扰度](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/solution/by-ac_oier-2rii/) | 中等 | 🤩🤩🤩🤩 |
5050
| [2047. 句子中的有效单词数](https://leetcode-cn.com/problems/number-of-valid-words-in-a-sentence/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-valid-words-in-a-sentence/solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-m-5pcz/) | 简单 | 🤩🤩🤩🤩 |
5151
| [面试题 01.05. 一次编辑](https://leetcode.cn/problems/one-away-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/one-away-lcci/solution/by-ac_oier-7ml0/) | 中等 | 🤩🤩🤩🤩 |
52+
| [面试题 17.11. 单词距离](https://leetcode.cn/problems/find-closest-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/find-closest-lcci/solution/by-ac_oier-0hv9/) | 中等 | 🤩🤩🤩🤩 |
5253

‎Index/模拟.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,5 @@
158158
| [2069. 模拟行走机器人 II](https://leetcode-cn.com/problems/walking-robot-simulation-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/walking-robot-simulation-ii/solution/by-ac_oier-6zib/) | 中等 | 🤩🤩🤩🤩 |
159159
| [面试题 01.05. 一次编辑](https://leetcode.cn/problems/one-away-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/one-away-lcci/solution/by-ac_oier-7ml0/) | 中等 | 🤩🤩🤩🤩 |
160160
| [面试题 10.02. 变位词组](https://leetcode-cn.com/problems/group-anagrams-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/group-anagrams-lcci/solution/gong-shui-san-xie-tong-ji-bian-wei-ci-de-0iqe/) | 中等 | 🤩🤩🤩🤩 |
161+
| [面试题 17.11. 单词距离](https://leetcode.cn/problems/find-closest-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/find-closest-lcci/solution/by-ac_oier-0hv9/) | 中等 | 🤩🤩🤩🤩 |
161162

‎Index/线段树.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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
| [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/) | 困难 | 🤩🤩🤩🤩🤩 |
5+
| [699. 掉落的方块](https://leetcode.cn/problems/falling-squares/) | [LeetCode 题解链接](https://leetcode.cn/problems/falling-squares/solution/by-ac_oier-zpf0/) | 困难 | 🤩🤩🤩🤩 |
56
| [729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/) | 中等 | 🤩🤩🤩🤩🤩 |
67
| [731. 我的日程安排表 II](https://leetcode-cn.com/problems/my-calendar-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-ii/solution/by-ac_oier-okkc/) | 中等 | 🤩🤩🤩🤩🤩 |
78
| [732. 我的日程安排表 III](https://leetcode-cn.com/problems/my-calendar-iii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-iii/solution/by-ac_oier-cv31/) | 困难 | 🤩🤩🤩🤩🤩 |

‎LeetCode/461-470/467. 环绕字符串中唯一的子字符串(中等).md‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ Tag : 「线性 DP」、「树状数组」
4545

4646
### 线性 DP + 树状数组 + 同字符最大长度计数
4747

48-
> 早上起来没睡醒,第一反应是用「线性 DP + 树状数组」来做,估了一下时间复杂度没问题就写了。
49-
该做法可能有一定的思维难度,因此可能不是这道中等题的标准解法。
48+
> ~~早上起来没睡醒~~ 老了,脑袋不行了,第一反应是用「线性 DP + 树状数组」来做,估了一下时间复杂度没问题就写了。
49+
该做法有一点点思维难度,因此可能不是这道中等题的标准解法。
5050

5151
**定义 $f[i]$ 为以 $s[i]$ 为结尾的最大有效子串的长度。**
5252

@@ -110,7 +110,37 @@ class Solution {
110110
}
111111
```
112112
* 时间复杂度:$O(n\log{n})$
113-
* 空间复杂度:$O(C \times N),ドル其中 $C = 26$ 为字符串 `p` 的字符集大小
113+
* 空间复杂度:$O(C \times n),ドル其中 $C = 26$ 为字符串 `p` 的字符集大小
114+
115+
---
116+
117+
### 线性 DP
118+
119+
对于相同的结尾字符 $c$ 而言,如果在整个动规过程中的最大长度为 $len,ドル那么以 $c$ 为结尾字符对答案的贡献为 $len$。
120+
121+
基于此,我们只需保留解法一中的 `max` 数组即可,同时利用 $f[i]$ 只依赖于 $f[i - 1]$ 进行更新,因此动规数组也可以使用一个变量来代替。
122+
123+
代码:
124+
```Java
125+
class Solution {
126+
public int findSubstringInWraproundString(String _p) {
127+
char[] cs = _p.toCharArray();
128+
int n = cs.length, ans = 0;
129+
int[] max = new int[26];
130+
max[cs[0] - 'a']++;
131+
for (int i = 1, j = 1; i < n; i++) {
132+
int c = cs[i] - 'a', p = cs[i - 1] - 'a';
133+
if ((p == 25 && c == 0) || p + 1 == c) j++;
134+
else j = 1;
135+
max[c] = Math.max(max[c], j);
136+
}
137+
for (int i = 0; i < 26; i++) ans += max[i];
138+
return ans;
139+
}
140+
}
141+
```
142+
* 时间复杂度:$O(n)$
143+
* 空间复杂度:$O(C),ドル其中 $C = 26$ 为字符串 `p` 的字符集大小
114144

115145
---
116146

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[699. 掉落的方块](https://leetcode-cn.com/problems/degree-of-an-array/solution/shu-zu-ji-shu-ha-xi-biao-ji-shu-jie-fa-y-a0mg/)** ,难度为 **困难**
4+
5+
Tag : 「线段树(动态开点)」、「线段树」
6+
7+
8+
9+
在无限长的数轴(即 `x` 轴)上,我们根据给定的顺序放置对应的正方形方块。
10+
11+
第 i 个掉落的方块(`positions[i] = (left, side_length)`)是正方形,其中 `left` 表示该方块最左边的点位置(`positions[i][0]`),`side_length` 表示该方块的边长(`positions[i][1]`)。
12+
13+
每个方块的底部边缘平行于数轴(即 `x` 轴),并且从一个比目前所有的落地方块更高的高度掉落而下。在上一个方块结束掉落,并保持静止后,才开始掉落新方块。
14+
15+
方块的底边具有非常大的粘性,并将保持固定在它们所接触的任何长度表面上(无论是数轴还是其他方块)。邻接掉落的边不会过早地粘合在一起,因为只有底边才具有粘性。
16+
17+
返回一个堆叠高度列表 `ans`。每一个堆叠高度 `ans[i]` 表示在通过 `positions[0], positions[1], ..., positions[i]` 表示的方块掉落结束后,目前所有已经落稳的方块堆叠的最高高度。
18+
19+
示例 1:
20+
```
21+
输入: [[1, 2], [2, 3], [6, 1]]
22+
23+
输出: [2, 5, 5]
24+
25+
解释:
26+
27+
第一个方块 positions[0] = [1, 2] 掉落:
28+
_aa
29+
_aa
30+
-------
31+
方块最大高度为 2 。
32+
33+
第二个方块 positions[1] = [2, 3] 掉落:
34+
__aaa
35+
__aaa
36+
__aaa
37+
_aa__
38+
_aa__
39+
--------------
40+
方块最大高度为5。
41+
大的方块保持在较小的方块的顶部,不论它的重心在哪里,因为方块的底部边缘有非常大的粘性。
42+
43+
第三个方块 positions[1] = [6, 1] 掉落:
44+
__aaa
45+
__aaa
46+
__aaa
47+
_aa
48+
_aa___a
49+
--------------
50+
方块最大高度为5。
51+
52+
因此,我们返回结果[2, 5, 5]。
53+
```
54+
55+
示例 2:
56+
```
57+
输入: [[100, 100], [200, 100]]
58+
59+
输出: [100, 100]
60+
61+
解释: 相邻的方块不会过早地卡住,只有它们的底部边缘才能粘在表面上。
62+
```
63+
64+
注意:
65+
* 1ドル <= positions.length <= 1000$
66+
* 1ドル <= positions[i][0] <= 10^8$
67+
* 1ドル <= positions[i][1] <= 10^6$
68+
69+
---
70+
71+
### 基本分析
72+
73+
为了方便,我们使用 `ps` 来代指 `positions`
74+
75+
每次从插入操作都附带一次询问,因此询问次数为 1ドルe3,ドル左端点的最大值为 10ドルe8,ドル边长最大值为 1ドルe6,ドル由此可知值域范围大于 1ドルe8,ドル但不超过 1ドルe9$。
76+
77+
对于值域范围大,但查询次数有限的区间和问题,不久前曾经总结过 : [求解常见「值域爆炸,查询有限」区间问题的几种方式](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247491187&idx=2&sn=bb2d8b7e89c535914da8107387e951a2),可作为前置 🧀 进行了解。
78+
79+
而我的个人习惯,一般要么使用「离散化 + 线段树」,要么使用「线段树(动态开点)」进行求解。
80+
81+
本题为「非强制在线」问题,因此可以先对 `ps` 数组进行离散化,将值域映射到较小的空间,然后套用固定占用 4ドル \times n$ 空间的线段树求解。
82+
83+
但更为灵活(能够同时应对强制在线问题)的求解方式是「线段树(动态开点)」。
84+
85+
同时实现动态开点的方式有两种:
86+
87+
1. 根据操作次数对使用到的最大点数进行预估,并采用数组方式进行实现线段树(解法一);
88+
2. 使用动态指针(解法二);
89+
90+
方式一在不久之前的每日一题 [933. 最近的请求次数](https://sharingsource.github.io/2022/05/06/933.%20%E6%9C%80%E8%BF%91%E7%9A%84%E8%AF%B7%E6%B1%82%E6%AC%A1%E6%95%B0%EF%BC%88%E7%AE%80%E5%8D%95%EF%BC%89/) 讲过,因此今天把方式二也写一下。
91+
92+
具体的,我们将顺序放置方块的操作(假设当前方块的左端点为 $a,ドル边长为 $len,ドル则有右端点为 $b = a + len$),分成如下两步进行:
93+
94+
* 查询当前范围 $[a, b]$ 的最大高度为多少,假设为 $cur$;
95+
* 更新当前范围 $[a, b]$ 的最新高度为 $cur + len$。
96+
97+
因此这本质上是一个「区间修改 + 区间查询」的问题,我们需要实现带「懒标记」的线段树,从而确保在进行「区间修改」时复杂度仍为 $O(\log{n})$。
98+
99+
> 另外有一个需要注意的细节是:不同方块之间的边缘可以重合,但不会导致方块叠加,因此我们当我们对一个区间 $[a, b]$ 进行操作(查询或插入)时,可以将其调整为 $[a, b - 1],ドル从而解决边缘叠加操作高度错误的问题。
100+
101+
---
102+
103+
### 线段树(动态开点 - 估点)
104+
105+
估点的基本方式在前置 🧀 [求解常见「值域爆炸,查询有限」区间问题的几种方式](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247491187&idx=2&sn=bb2d8b7e89c535914da8107387e951a2) 详细讲过。
106+
107+
简单来说,可以直接估算为 6ドル \times m \times \log{n}$ 即可,其中 $m$ 为询问次数(对应本题就是 `ps` 的长度),而 $n$ 为值域大小(对应本题可直接取成 1ドルe9$);而另外一个比较实用(避免估算)的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开(不考虑字节对齐,或者结构体过大的情况,`Java` 的 128ドルM$ 可以开到 5ドル \times 10^6$ 以上)。
108+
109+
代码:
110+
```Java
111+
class Solution {
112+
class Node {
113+
// ls 和 rs 分别代表当前区间的左右子节点所在 tr 数组中的下标
114+
// val 代表当前区间的最大高度,add 为懒标记
115+
int ls, rs, val, add;
116+
}
117+
int N = (int)1e9, cnt = 0;
118+
Node[] tr = new Node[1000010];
119+
void update(int u, int lc, int rc, int l, int r, int v) {
120+
if (l <= lc && rc <= r) {
121+
tr[u].val = v;
122+
tr[u].add = v;
123+
return ;
124+
}
125+
pushdown(u);
126+
int mid = lc + rc >> 1;
127+
if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);
128+
if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);
129+
pushup(u);
130+
}
131+
int query(int u, int lc, int rc, int l, int r) {
132+
if (l <= lc && rc <= r) return tr[u].val;
133+
pushdown(u);
134+
int mid = lc + rc >> 1, ans = 0;
135+
if (l <= mid) ans = query(tr[u].ls, lc, mid, l, r);
136+
if (r > mid) ans = Math.max(ans, query(tr[u].rs, mid + 1, rc, l, r));
137+
return ans;
138+
}
139+
void pushdown(int u) {
140+
if (tr[u] == null) tr[u] = new Node();
141+
if (tr[u].ls == 0) {
142+
tr[u].ls = ++cnt;
143+
tr[tr[u].ls] = new Node();
144+
}
145+
if (tr[u].rs == 0) {
146+
tr[u].rs = ++cnt;
147+
tr[tr[u].rs] = new Node();
148+
}
149+
if (tr[u].add == 0) return ;
150+
int add = tr[u].add;
151+
tr[tr[u].ls].add = add; tr[tr[u].rs].add = add;
152+
tr[tr[u].ls].val = add; tr[tr[u].rs].val = add;
153+
tr[u].add = 0;
154+
}
155+
void pushup(int u) {
156+
tr[u].val = Math.max(tr[tr[u].ls].val, tr[tr[u].rs].val);
157+
}
158+
public List<Integer> fallingSquares(int[][] ps) {
159+
List<Integer> ans = new ArrayList<>();
160+
tr[1] = new Node();
161+
for (int[] info : ps) {
162+
int x = info[0], h = info[1], cur = query(1, 1, N, x, x + h - 1);
163+
update(1, 1, N, x, x + h - 1, cur + h);
164+
ans.add(tr[1].val);
165+
}
166+
return ans;
167+
}
168+
}
169+
```
170+
* 时间复杂度:令 $m$ 为查询次数,$n$ 为值域大小,复杂度为 $O(m\log{n})$
171+
* 空间复杂度:$O(m\log{n})$
172+
173+
---
174+
175+
### 线段树(动态开点 - 动态指针)
176+
177+
利用「动态指针」实现的「动态开点」可以有效避免数组估点问题,更重要的是可以有效避免 `new` 大数组的初始化开销,对于 LC 这种还跟你算所有样例总时长的 OJ 来说,在不考虑 `static` 优化/全局数组优化 的情况下,动态指针的方式要比估点的方式来得好。
178+
179+
代码:
180+
```Java
181+
class Solution {
182+
int N = (int)1e9;
183+
class Node {
184+
// ls 和 rs 分别代表当前区间的左右子节点
185+
Node ls, rs;
186+
// val 代表当前区间的最大高度,add 为懒标记
187+
int val, add;
188+
}
189+
Node root = new Node();
190+
void update(Node node, int lc, int rc, int l, int r, int v) {
191+
if (l <= lc && rc <= r) {
192+
node.add = v;
193+
node.val = v;
194+
return ;
195+
}
196+
pushdown(node);
197+
int mid = lc + rc >> 1;
198+
if (l <= mid) update(node.ls, lc, mid, l, r, v);
199+
if (r > mid) update(node.rs, mid + 1, rc, l, r, v);
200+
pushup(node);
201+
}
202+
int query(Node node, int lc, int rc, int l, int r) {
203+
if (l <= lc && rc <= r) return node.val;
204+
pushdown(node);
205+
int mid = lc + rc >> 1, ans = 0;
206+
if (l <= mid) ans = query(node.ls, lc, mid, l, r);
207+
if (r > mid) ans = Math.max(ans, query(node.rs, mid + 1, rc, l, r));
208+
return ans;
209+
}
210+
void pushdown(Node node) {
211+
if (node.ls == null) node.ls = new Node();
212+
if (node.rs == null) node.rs = new Node();
213+
if (node.add == 0) return ;
214+
node.ls.add = node.add; node.rs.add = node.add;
215+
node.ls.val = node.add; node.rs.val = node.add;
216+
node.add = 0;
217+
}
218+
void pushup(Node node) {
219+
node.val = Math.max(node.ls.val, node.rs.val);
220+
}
221+
public List<Integer> fallingSquares(int[][] ps) {
222+
List<Integer> ans = new ArrayList<>();
223+
for (int[] info : ps) {
224+
int x = info[0], h = info[1], cur = query(root, 0, N, x, x + h - 1);
225+
update(root, 0, N, x, x + h - 1, cur + h);
226+
ans.add(root.val);
227+
}
228+
return ans;
229+
}
230+
}
231+
```
232+
* 时间复杂度:令 $m$ 为查询次数,$n$ 为值域大小,复杂度为 $O(m\log{n})$
233+
* 空间复杂度:$O(m\log{n})$
234+
235+
---
236+
237+
### 最后
238+
239+
这是我们「刷穿 LeetCode」系列文章的第 `No.699` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
240+
241+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
242+
243+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
244+
245+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
246+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[面试题 17.11. 单词距离](https://leetcode.cn/problems/find-closest-lcci/solution/by-ac_oier-0hv9/)** ,难度为 **中等**
4+
5+
Tag : 「模拟」、「双指针」
6+
7+
8+
9+
有个内含单词的超大文本文件,给定任意两个不同的单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗?
10+
11+
示例:
12+
```
13+
输入:words = ["I","am","a","student","from","a","university","in","a","city"], word1 = "a", word2 = "student"
14+
15+
输出:1
16+
```
17+
提示:
18+
* $words.length <= 100000$
19+
20+
---
21+
22+
### 模拟
23+
24+
`words` 进行遍历,使用两个指针 `p``q` 分别记录最近的两个关键字的位置,并维护更新最小距离。
25+
26+
代码:
27+
```Java
28+
class Solution {
29+
public int findClosest(String[] ws, String a, String b) {
30+
int n = ws.length, ans = n;
31+
for (int i = 0, p = -1, q = -1; i < n; i++) {
32+
String t = ws[i];
33+
if (t.equals(a)) p = i;
34+
if (t.equals(b)) q = i;
35+
if (p != -1 && q != -1) ans = Math.min(ans, Math.abs(p - q));
36+
}
37+
return ans;
38+
}
39+
}
40+
```
41+
* 时间复杂度:$O(n)$
42+
* 空间复杂度:$O(1)$
43+
44+
---
45+
46+
### 最后
47+
48+
这是我们「刷穿 LeetCode」系列文章的第 `No.面试题 17.11` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
49+
50+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
51+
52+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
53+
54+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
55+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /