主题
思路
“若我俩都走过同样的路,则我俩心意相通。”
假设字符串里面只有
从左到右遍历字符串,把
:例如先插入字符串 ,相当于生成了一条移动方向为「左-左-右」的路径。标记最后一个节点为终止节点。再插入字符串 ,相当于生成了一条移动方向为「左-左-右-右」的路径。标记最后一个节点为终止节点。 :例如查找字符串 ,相当于查找二叉树中是否存在一条移动方向为「左-左-右」的路径,且最后一个节点是终止节点。 :例如查找前缀 ,相当于查找二叉树中是否存在一条移动方向为「左-左」的路径,无其他要求。

推广到
算法
- 初始化:创建一棵
叉树,一开始只有一个根节点 。 叉树的每个节点包含一个长为 的儿子节点列表 ,以及一个布尔值 ,表示是否为终止节点。 : - 遍历字符串
,同时用一个变量 表示当前在 叉树的哪个节点,初始值为 。 - 如果
不是 的儿子,那么创建一个新的节点 作为 的儿子。如果 ,那么把 记录到 的 中。如果 ,那么把 记录到 的 中。依此类推。 - 更新
为儿子列表中的相应节点。 - 遍历结束,把
的 标记为 。
- 遍历字符串
和 可以复用同一个函数 : - 遍历字符串
,同时用一个变量 表示当前在 叉树的哪个节点,初始值为 。 - 如果
不是 的儿子,返回 。 和 收到 之后返回 。 - 更新
为儿子列表中的相应节点。 - 遍历结束,如果
的 是 ,返回 ,否则返回 。 如果收到的是 ,返回 ,否则返回 。 如果收到的是非 数字,返回 ,否则返回 。
- 遍历字符串
python
class Node:
__slots__ = 'son', 'end'
def __init__(self):
self.son = {}
self.end = False
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word: str) -> None:
cur = self.root
for c in word:
if c not in cur.son: # 无路可走?
cur.son[c] = Node() # 那就造路!
cur = cur.son[c]
cur.end = True
def find(self, word: str) -> int:
cur = self.root
for c in word:
if c not in cur.son: # 道不同,不相为谋
return 0
cur = cur.son[c]
# 走过同样的路(2=完全匹配,1=前缀匹配)
return 2 if cur.end else 1
def search(self, word: str) -> bool:
return self.find(word) == 2
def startsWith(self, prefix: str) -> bool:
return self.find(prefix) != 0cpp
// C++ 版待补充python
class Node:
__slots__ = 'son', 'end'
def __init__(self):
self.son = [None] * 26
self.end = False
class Trie:
def __init__(self):
self.root = Node()
def insert(self, word: str) -> None:
cur = self.root
for c in word:
c = ord(c) - ord('a')
if cur.son[c] is None: # 无路可走?
cur.son[c] = Node() # 那就造路!
cur = cur.son[c]
cur.end = True
def find(self, word: str) -> int:
cur = self.root
for c in word:
c = ord(c) - ord('a')
if cur.son[c] is None: # 道不同,不相为谋
return 0
cur = cur.son[c]
# 走过同样的路(2=完全匹配,1=前缀匹配)
return 2 if cur.end else 1
def search(self, word: str) -> bool:
return self.find(word) == 2
def startsWith(self, prefix: str) -> bool:
return self.find(prefix) != 0cpp
// C++ 版待补充cpp
struct Node {
Node* son[26]{};
bool end = false;
};
class Trie {
Node* root = new Node();
int find(string word) {
Node* cur = root;
for (char c : word) {
c -= 'a';
if (cur->son[c] == nullptr) { // 道不同,不相为谋
return 0;
}
cur = cur->son[c];
}
// 走过同样的路(2=完全匹配,1=前缀匹配)
return cur->end ? 2 : 1;
}
void destroy(Node* node) {
if (node == nullptr) {
return;
}
for (Node* son : node->son) {
destroy(son);
}
delete node;
}
public:
~Trie() {
destroy(root);
}
void insert(string word) {
Node* cur = root;
for (char c : word) {
c -= 'a';
if (cur->son[c] == nullptr) { // 无路可走?
cur->son[c] = new Node(); // new 出来!
}
cur = cur->son[c];
}
cur->end = true;
}
bool search(string word) {
return find(word) == 2;
}
bool startsWith(string prefix) {
return find(prefix) != 0;
}
};复杂度分析
- 时间复杂度:初始化为
, 为 ,其余为 ,其中 是 的长度, 是字符集合的大小。注意创建一个节点需要 的时间(如果用的是数组)。 - 空间复杂度:
。其中 是 的调用次数。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府