Hard
题目描述
设计一个特殊的字典,可以通过前缀和后缀来搜索其中的单词。
实现 WordFilter 类:
WordFilter(string[] words)用字典中的单词数组初始化对象。f(string pref, string suff)返回字典中具有前缀pref和后缀suff的单词的索引。如果有多个有效索引,返回其中最大的一个。如果字典中不存在这样的单词,返回-1。
示例 1:
输入
["WordFilter", "f"]
[[["apple"]], ["a", "e"]]
输出
[null, 0]
解释
WordFilter wordFilter = new WordFilter(["apple"]);
wordFilter.f("a", "e"); // 返回 0,因为索引为 0 的单词前缀是 "a",后缀是 "e"
约束条件:
1 <= words.length <= 10^41 <= words[i].length <= 71 <= pref.length, suff.length <= 7words[i]、pref和suff仅由小写英文字母组成- 最多调用
f函数10^4次
解题思路
这道题要求设计一个支持前缀和后缀搜索的数据结构。核心思路是将前缀和后缀的匹配问题转化为前缀匹配问题。
解法一:字典树(Trie)+ 组合字符串 关键思想是将每个单词的所有可能的后缀-前缀组合存储到字典树中。具体做法:
- 对于单词 “apple”,我们生成组合字符串:"{apple", “e{apple”, “le{apple”, “ple{apple”, “pple{apple”, “apple{apple”
- 使用特殊字符 ‘{’ 作为分隔符(ASCII码紧跟在 ‘z’ 后面)
- 当查询前缀 “app” 和后缀 “le” 时,我们在字典树中搜索 “le{app”
解法二:哈希表暴力 对于每次查询,遍历所有单词检查前缀和后缀匹配。虽然简单但效率较低。
推荐使用字典树解法,因为它在多次查询时具有更好的性能。字典树中每个节点存储权重(索引),确保返回最大的有效索引。
代码实现
class TrieNode {
public:
TrieNode* children[27];
int weight;
TrieNode() {
for (int i = 0; i < 27; i++) {
children[i] = nullptr;
}
weight = -1;
}
};
class WordFilter {
private:
TrieNode* root;
void insertWord(const string& word, int index) {
for (int i = 0; i <= word.length(); i++) {
TrieNode* cur = root;
cur->weight = index;
for (int j = i; j < word.length(); j++) {
int idx = word[j] - 'a';
if (!cur->children[idx]) {
cur->children[idx] = new TrieNode();
}
cur = cur->children[idx];
cur->weight = index;
}
cur = cur->children[26];
if (!cur) {
root->children[26] = new TrieNode();
cur = root->children[26];
}
cur->weight = index;
for (int j = 0; j < word.length(); j++) {
int idx = word[j] - 'a';
if (!cur->children[idx]) {
cur->children[idx] = new TrieNode();
}
cur = cur->children[idx];
cur->weight = index;
}
}
}
public:
WordFilter(vector<string>& words) {
root = new TrieNode();
for (int i = 0; i < words.size(); i++) {
insertWord(words[i], i);
}
}
int f(string pref, string suff) {
TrieNode* cur = root;
for (char c : suff) {
int idx = c - 'a';
if (!cur->children[idx]) return -1;
cur = cur->children[idx];
}
if (!cur->children[26]) return -1;
cur = cur->children[26];
for (char c : pref) {
int idx = c - 'a';
if (!cur->children[idx]) return -1;
cur = cur->children[idx];
}
return cur->weight;
}
};
class TrieNode:
def __init__(self):
self.children = [None] * 27
self.weight = -1
class WordFilter:
def __init__(self, words: List[str]):
self.root = TrieNode()
for index, word in enumerate(words):
self._insert_word(word, index)
def _insert_word(self, word, index):
for i in range(len(word) + 1):
cur = self.root
cur.weight = index
for j in range(i, len(word)):
idx = ord(word[j]) - ord('a')
if not cur.children[idx]:
cur.children[idx] = TrieNode()
cur = cur.children[idx]
cur.weight = index
if not cur.children[26]:
cur.children[26] = TrieNode()
cur = cur.children[26]
cur.weight = index
for j in range(len(word)):
idx = ord(word[j]) - ord('a')
if not cur.children[idx]:
cur.children[idx] = TrieNode()
cur = cur.children[idx]
cur.weight = index
def f(self, pref: str, suff: str) -> int:
cur = self.root
for c in suff:
idx = ord(c) - ord('a')
if not cur.children[idx]:
return -1
cur = cur.children[idx]
if not cur.children[26]:
return -1
cur = cur.children[26]
for c in pref:
idx = ord(c) - ord('a')
if not cur.children[idx]:
return -1
cur = cur.children[idx]
return cur.weight
public class TrieNode {
public TrieNode[] Children = new TrieNode[27];
public int Weight = -1;
}
public class WordFilter {
private TrieNode root;
public WordFilter(string[] words) {
root = new TrieNode();
for (int i = 0; i < words.Length; i++) {
InsertWord(words[i], i);
}
}
private void InsertWord(string word, int index) {
for (int i = 0; i <= word.Length; i++) {
TrieNode cur = root;
cur.Weight = index;
for (int j = i; j < word.Length; j++) {
int idx = word[j] - 'a';
if (cur.Children[idx] == null) {
cur.Children[idx] = new TrieNode();
}
cur = cur.Children[idx];
cur.Weight = index;
}
if (cur.Children[26] == null) {
cur.Children[26] = new TrieNode();
}
cur = cur.Children[26];
cur.Weight = index;
for (int j = 0; j < word.Length; j++) {
int idx = word[j] - 'a';
if (cur.Children[idx] == null) {
cur.Children[idx] = new TrieNode();
}
cur = cur.Children[idx];
cur.Weight = index;
}
}
}
public int F(string pref, string suff) {
TrieNode cur = root;
foreach (char c in suff) {
int idx = c - 'a';
if (cur.Children[idx] == null) return -1;
cur = cur.Children[idx];
}
if (cur.Children[26] == null) return -1;
cur = cur.Children[26];
foreach (char c in pref) {
int idx = c - 'a';
if (cur.Children[idx] == null) return -1;
cur = cur.Children[idx];
}
return cur.Weight;
}
}
class TrieNode {
constructor() {
this.children = new Array(27).fill(null);
this.weight = -1;
}
}
var WordFilter = function(words) {
this.root = new TrieNode();
for (let i = 0; i < words.length; i++) {
this.insertWord(words[i], i);
}
};
WordFilter.prototype.insertWord = function(word, index) {
for (let i = 0; i <= word.length; i++) {
let cur = this.root;
cur.weight = index;
for (let j = i; j < word.length; j++) {
let idx = word.charCodeAt(j) - 97;
if (!cur.children[idx]) {
cur.children[idx] = new TrieNode();
}
cur = cur.children[idx];
cur.weight = index;
}
if (!cur.children[26]) {
cur.children[26] = new TrieNode();
}
cur = cur.children[26];
cur.weight = index;
for (let j = 0; j < word.length; j++) {
let idx = word.charCodeAt(j) - 97;
if (!cur.children[idx]) {
cur.children[idx] = new TrieNode();
}
cur = cur.children[idx];
cur.weight = index;
}
}
};
WordFilter.prototype.f = function(pref, suff) {
let cur = this.root;
for (let c of suff) {
let idx = c.charCodeAt(0) - 97;
if (!cur.children[idx]) return -1;
cur = cur.children[idx];
}
if (!cur.children[26]) return -1;
cur = cur.children[26];
for (let c of pref) {
let idx = c.charCodeAt(0) - 97;
if (!cur.children[idx]) return -1;
cur = cur.children[idx];
}
return cur.weight;
};
复杂度分析
| 复杂度 | 字典树解法 |
|---|---|
| 时间复杂度 | 构造:O(N × L²),查询:O(P + S) |
| 空间复杂度 | O(N × L²) |
其中 N 为单词数量,L 为单词平均长度,P 为前缀长度,S 为后缀长度。