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 0936789

Browse files
Merge pull request SharingSource#660 from SharingSource/ac_oier
✨feat: add 1032
2 parents 6dd9f80 + 1d9071c commit 0936789

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

‎Index/字典树.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| [677. 键值映射](https://leetcode-cn.com/problems/map-sum-pairs/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/map-sum-pairs/solution/gong-shui-san-xie-jie-he-dfs-de-trie-yun-i4xa/) | 中等 | 🤩🤩🤩🤩 |
1010
| [720. 词典中最长的单词](https://leetcode-cn.com/problems/longest-word-in-dictionary/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/longest-word-in-dictionary/solution/by-ac_oier-bmot/) | 简单 | 🤩🤩🤩🤩 |
1111
| [745. 前缀和后缀搜索](https://leetcode.cn/problems/prefix-and-suffix-search/) | [LeetCode 题解链接](https://leetcode.cn/problems/prefix-and-suffix-search/solution/by-ac_oier-ayej/) | 困难 | 🤩🤩🤩🤩 |
12+
| [1032. 字符流](https://leetcode.cn/problems/stream-of-characters/) | [LeetCode 题解链接](https://leetcode.cn/problems/stream-of-characters/solution/by-ac_oier-ihd4/) | 困难 | 🤩🤩🤩🤩🤩 |
1213
| [1707. 与数组中元素的最大异或值](https://leetcode-cn.com/problems/maximum-xor-with-an-element-from-array/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximum-xor-with-an-element-from-array/solution/gong-shui-san-xie-jie-zhe-ge-wen-ti-lai-lypqr/) | 困难 | 🤩🤩🤩 |
1314
| [剑指 Offer II 067. 最大的异或](https://leetcode.cn/problems/ms70jA/) | [LeetCode 题解链接](https://leetcode.cn/problems/ms70jA/solution/by-ac_oier-d9kx/) | 中等 | 🤩🤩🤩🤩 |
1415

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1032. 字符流](https://leetcode.cn/problems/stream-of-characters/solution/by-ac_oier-ihd4/)** ,难度为 **困难**
4+
5+
Tag : 「字典树」、「枚举」、「剪枝」
6+
7+
8+
9+
设计一个算法:接收一个字符流,并检查这些字符的后缀是否是字符串数组 `words` 中的一个字符串。
10+
11+
例如,`words = ["abc", "xyz"]` 且字符流中逐个依次加入 4ドル$ 个字符 `'a'``'x'``'y'``'z'` ,你所设计的算法应当可以检测到 `"axyz"` 的后缀 `"xyz"``words` 中的字符串 `"xyz"` 匹配。
12+
13+
按下述要求实现 `StreamChecker` 类:
14+
15+
* `StreamChecker(String[] words)` :构造函数,用字符串数组 `words` 初始化数据结构。
16+
* `boolean query(char letter)`:从字符流中接收一个新字符,如果字符流中的任一非空后缀能匹配 `words` 中的某一字符串,返回 `true`;否则,返回 `false`
17+
18+
示例:
19+
```
20+
输入:
21+
["StreamChecker", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query"]
22+
[[["cd", "f", "kl"]], ["a"], ["b"], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["i"], ["j"], ["k"], ["l"]]
23+
24+
输出:
25+
[null, false, false, false, true, false, true, false, false, false, false, false, true]
26+
27+
解释:
28+
StreamChecker streamChecker = new StreamChecker(["cd", "f", "kl"]);
29+
streamChecker.query("a"); // 返回 False
30+
streamChecker.query("b"); // 返回 False
31+
streamChecker.query("c"); // 返回n False
32+
streamChecker.query("d"); // 返回 True ,因为 'cd' 在 words 中
33+
streamChecker.query("e"); // 返回 False
34+
streamChecker.query("f"); // 返回 True ,因为 'f' 在 words 中
35+
streamChecker.query("g"); // 返回 False
36+
streamChecker.query("h"); // 返回 False
37+
streamChecker.query("i"); // 返回 False
38+
streamChecker.query("j"); // 返回 False
39+
streamChecker.query("k"); // 返回 False
40+
streamChecker.query("l"); // 返回 True ,因为 'kl' 在 words 中
41+
```
42+
43+
提示:
44+
* 1ドル <= words.length <= 2000$
45+
* 1ドル <= words[i].length <= 200$
46+
* `words[i]` 由小写英文字母组成
47+
* `letter` 是一个小写英文字母
48+
* 最多调用查询 4ドル \times 10^4$ 次
49+
50+
---
51+
52+
### Trie + 枚举
53+
54+
先考虑最为简单的做法:将给定的所有 $words[i]$ 顺序插入字典树,根据数据范围可知这一步计算量为 2000ドル \times 200,ドル其中最大的 $words[i]$ 长度只有 200ドル$。
55+
56+
然后利用$words[i]$ 长度只有 200ドル$ 这一条件,直接使用「枚举」的方式来实现 `query`
57+
58+
具体的,我们可以先使用一个字符串 `s` 来记录 `query` 操作产生的数据流,然后实现一个 `boolean query(int start, int end)` 方法,该方法会检查字典树中是否存在 $s[i...j]$ 子串。
59+
60+
由于 $words[i]$ 长度只有 200ドル$(假设当前 `s` 的长度为 $n$),因此我们只需要枚举「$\max(0, n - 200)$ 作为子串左端点,$n - 1$ 作为子串右端点」是否存在字典树中(是否存在 $words[i]$ 中)即可,最坏情况下,单次 `query` 操作计算量为 200ドル \times 200$。
61+
62+
> 一些细节:为了避免每个样例都 `new` 大数组,我们可以使用 `static` 优化。
63+
64+
代码:
65+
```Java
66+
class StreamChecker {
67+
static int N = 2010 * 200, idx = 0;
68+
static int[][] tr = new int[N][26];
69+
static boolean[] isEnd = new boolean[N * 26];
70+
StringBuilder sb = new StringBuilder();
71+
void add(String s) {
72+
int p = 0;
73+
for (int i = 0; i < s.length(); i++) {
74+
int u = s.charAt(i) - 'a';
75+
if (tr[p][u] == 0) tr[p][u] = ++idx;
76+
p = tr[p][u];
77+
}
78+
isEnd[p] = true;
79+
}
80+
boolean query(int start, int end) {
81+
int p = 0;
82+
for (int i = start; i <= end; i++) {
83+
int u = sb.charAt(i) - 'a';
84+
if (tr[p][u] == 0) return false;
85+
p = tr[p][u];
86+
}
87+
return isEnd[p];
88+
}
89+
public StreamChecker(String[] words) {
90+
for (int i = 0; i <= idx; i++) {
91+
Arrays.fill(tr[i], 0);
92+
isEnd[i] = false;
93+
}
94+
idx = 0;
95+
for (String s : words) add(s);
96+
}
97+
public boolean query(char c) {
98+
sb.append(c);
99+
int n = sb.length(), min = Math.max(0, n - 200);
100+
for (int i = n - 1; i >= min; i--) {
101+
if (query(i, n - 1)) return true;
102+
}
103+
return false;
104+
}
105+
}
106+
```
107+
* 时间复杂度:`StreamChecker` 初始化复杂度为 $O(n),ドル其中 $n$ 为 `words` 字符总数;`query` 操作复杂度为 $O(m^2),ドル其中 $m = 200$ 为最大 `words[i]` 长度
108+
* 空间复杂度:$O(n \times C),ドル其中 $n$ 为 `words` 字符总数,$C = 26$ 为字符集大小
109+
110+
---
111+
112+
### Trie(优化)
113+
114+
初始化将所有的 $words[i]$ 存入 `Trie` 是必然的,我们只能考虑如何优化 `query` 操作。
115+
116+
在解法一中,我们需要对新数据流对应的字符串的每个后缀进行搜索,同时每次搜索是相互独立的,即本次匹配不会对下一次匹配产生贡献。
117+
118+
**实际上,我们可以通过「倒序建 `Trie`」的方式,将「枚举检查多个后缀」的操作变为「匹配一次后缀」操作。**
119+
120+
具体的,我们可以在初始化 `StreamChecker` 时,将每个 $words[i]$ 翻转(倒序)加入 `Trie` 中;然后在 `query` 操作时(假设当前数据流对应的字符串为 `s`,长度为 $n$),从 `s` 的尾部开始在 `Trie` 中进行检索(即从 $s[n - 1]$ 开始往回找)。
121+
122+
若在某个位置 `idx` 时匹配成功,意味着 $s[idx ... (n-1)]$ 的翻转子串在字典树中,同时我们又是将每个 `words[i]` 进行倒序插入,即意味着 $s[idx ... (n - 1)]$ 的正向子串在 `words` 中,即满足 `s` 的某个后缀出现在 `words` 中。
123+
124+
同理,我们可以利用最大的 `words[i]` 长度为 200ドル$ 来控制从 $s[n - 1]$ 开始往回找的最远距离,同时利用当某个短后缀不在 `Trie` 中,则其余长度更大的后缀必然不在 `Trie` 中进行剪枝操作。
125+
126+
代码:
127+
```Java
128+
class StreamChecker {
129+
static int N = 2010 * 200, idx = 0;
130+
static int[][] tr = new int[N][26];
131+
static boolean[] isEnd = new boolean[N * 26];
132+
StringBuilder sb = new StringBuilder();
133+
void add(String s) {
134+
int p = 0;
135+
for (int i = s.length() - 1; i >= 0; i--) {
136+
int u = s.charAt(i) - 'a';
137+
if (tr[p][u] == 0) tr[p][u] = ++idx;
138+
p = tr[p][u];
139+
}
140+
isEnd[p] = true;
141+
}
142+
public StreamChecker(String[] words) {
143+
for (int i = 0; i <= idx; i++) {
144+
Arrays.fill(tr[i], 0);
145+
isEnd[i] = false;
146+
}
147+
idx = 0;
148+
for (String s : words) add(s);
149+
}
150+
public boolean query(char c) {
151+
sb.append(c);
152+
int n = sb.length(), min = Math.max(0, n - 200), p = 0;
153+
for (int i = n - 1; i >= min; i--) {
154+
if (isEnd[p]) return true;
155+
int u = sb.charAt(i) - 'a';
156+
if (tr[p][u] == 0) return false;
157+
p = tr[p][u];
158+
}
159+
return isEnd[p];
160+
}
161+
}
162+
```
163+
* 时间复杂度:`StreamChecker` 初始化复杂度为 $O(n),ドル其中 $n$ 为 `words` 字符总数;`query` 操作复杂度为 $O(m),ドル其中 $m = 200$ 为最大 `words[i]` 长度
164+
* 空间复杂度:$O(n \times C),ドル其中 $n$ 为 `words` 字符总数,$C = 26$ 为字符集大小
165+
166+
---
167+
168+
### 最后
169+
170+
这是我们「刷穿 LeetCode」系列文章的第 `No.1032` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
171+
172+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
173+
174+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
175+
176+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
177+

0 commit comments

Comments
(0)

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