题目描述
按下述要求实现 StreamChecker
类:
StreamChecker(words)
:构造函数,用给定的字词初始化数据结构。query(letter)
:如果存在某些k >= 1
,可以用查询的最后k
个字符(按从旧到新顺序,包括刚刚查询的字母)拼写出给定字词表中的某一字词时,返回true
。否则,返回false
。
样例
StreamChecker streamChecker = new StreamChecker(["cd","f","kl"]); // 初始化字典
streamChecker.query('a'); // 返回 false
streamChecker.query('b'); // 返回 false
streamChecker.query('c'); // 返回 false
streamChecker.query('d'); // 返回 true,因为 'cd' 在字词表中
streamChecker.query('e'); // 返回 false
streamChecker.query('f'); // 返回 true,因为 'f' 在字词表中
streamChecker.query('g'); // 返回 false
streamChecker.query('h'); // 返回 false
streamChecker.query('i'); // 返回 false
streamChecker.query('j'); // 返回 false
streamChecker.query('k'); // 返回 false
streamChecker.query('l'); // 返回 true,因为 'kl' 在字词表中。
注意
1 <= words.length <= 2000
1 <= words[i].length <= 2000
- 字词只包含小写英文字母。
- 待查项只包含小写英文字母。
- 待查项最多 40000 个。
算法
(AC 自动机) $O(N + Q)$
- AC 自动机模板题,这里只简要说明一下 AC 自动机的思想。
- 首先根据所有的模式串构建 Trie (字典)树,然后通过宽度优先遍历在 Trie 树上构建失败指针,即如果当前边匹配失败,该回到哪里重新匹配。这里的思想类似于 KMP 的思想。
- 这里需要注意的是,在宽度优先遍历的过程中,需要将 Trie 树中,标记为是单词终点的结点通过失败指针进行继承。
时间复杂度
- 假设 N 为字典树中的结点个数,Q 为总的询问长度,时间复杂度为 $O(N + Q)$。
空间复杂度
- 需要存储字典树,故空间复杂度为 $O(N)$。
C++ 代码
struct Node {
Node *nxt[26], *fail;
bool is_word;
Node() {
memset(nxt, NULL, sizeof(nxt));
fail = NULL;
is_word = false;
}
};
class StreamChecker {
public:
Node *root, *now;
StreamChecker(vector<string>& words) {
root = new Node();
for (auto &word : words)
insert(word);
build();
now = root;
}
void insert(string &word) {
Node *p = root;
for (auto &c : word) {
if (p -> nxt[c - 'a'] == NULL)
p -> nxt[c - 'a'] = new Node();
p = p -> nxt[c - 'a'];
}
p -> is_word = true;
}
void build() {
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node *sta = q.front();
q.pop();
for (int i = 0; i < 26; i++)
if (sta -> nxt[i] != NULL) {
Node *f = sta -> fail;
while (f != NULL && f -> nxt[i] == NULL)
f = f -> fail;
if (f != NULL) {
sta -> nxt[i] -> fail = f -> nxt[i];
// 通过失败指针进行继承
sta -> nxt[i] -> is_word |= f -> nxt[i] -> is_word;
}
else
sta -> nxt[i] -> fail = root;
q.push(sta -> nxt[i]);
}
}
root -> fail = root;
}
bool query(char letter) {
while (now != root && now -> nxt[letter - 'a'] == NULL)
now = now -> fail;
if (now -> nxt[letter - 'a'])
now = now -> nxt[letter - 'a'];
return now -> is_word;
}
};
/**
* Your StreamChecker object will be instantiated and called as such:
* StreamChecker* obj = new StreamChecker(words);
* bool param_1 = obj->query(letter);
*/
倒序插入和查询应该用不到ac自动机吧,而且时间复杂度也一样的
倒序具体指的是什么?这里要求是在线操作
就是插入时候字符串逆序插入,比较时候就可以正序比较了,可以看我的解法
https://www.acwing.com/solution/content/77094/
赞
问个比较蠢的问题,大佬能解释下吗?一个是build 方法中 sta -> nxt[i] -> is_word |= f -> nxt[i] -> is_word; 还一个是 query方法中的while那块没看,是重启自动机吗?能解释下不
第一个是更新is word的标记。
对于query,每次我们都要从上次的位置继续开始,所以要先通过失败指针找到一个开始位置
想求问一下这题AC自动机的思路是什么。感觉题目和AC自动机模板题好像不太像。
相当于把所有询问的字符变成一个字符串,就是待匹配串,问待匹配串中哪些地方是符合模板串的。符合模板串的位置就是 true。
每进来一个字符 从上个字符匹配结束的状态开始匹配嘛?
是的,可以在对象中存储上一个状态。
多谢,我当时想的就是把字典倒序建Trie树,然后把查询串也倒序 去Trie树中找。