Given many words, words[i] has weight i.
Design a class WordFilter that supports one function, WordFilter.
f(String prefix, String suffix). It will return the word with given prefix and suffix with maximum weight.
If no word exists, return -1.
题意:设计一个WordFilter类,它提供一个基本的搜索函数WordFilter.f(String prefix, String suffix),
根据前缀prefix和后缀suffix查询权值最大的单词并返回其下标。
Examples:
Input:
WordFilter(["apple"])
WordFilter.f("a", "e") // returns 0
WordFilter.f("b", "") // returns -1
Note:
words has length in range [1, 15000].
For each test case, up to words.length queries WordFilter.f may be made.
words[i] has length in range [1, 10].
prefix, suffix have lengths in range [0, 10].
words[i] and prefix, suffix queries consist of lowercase letters only.
思路:凡是涉及大量单词的频繁查询,必用到前缀树(字典树)。我们定义两棵字典树,其中一棵存储正序单词,另一颗存储逆序单词。在构造函数中,我们分别将每个单词的正序和逆序加入到对应的字典树中即可。在f函数中,我们首先找到前缀为prefix的所有单词所对应的索引(其值等同于权重),然后找到后缀为suffix的所有单词所对应的索引。接着的问题就是在这两个索引列表中查找共同最大元素了。
构造前缀树类(字典树/TRIE树)
关于前缀树的内容,戳这里,本文不再赘述。trieNode结点上增加一个存储单词索引值的变量,查找时返回所有符合条件的单词的索引值。
struct trieNode {
bool isEnd;
int index;
vector<trieNode*> children;
trieNode() {
isEnd = false;
index = -1;
children = vector<trieNode*>(26, 0);
}
};
//向前缀树root中插入新单词word,其索引值为index;
//word参数不要使用引用,这样addWord函数可以直接以常字符串为输入;
void addWord(trieNode *root, string word, int index);
//根据前缀查询单词,返回所有符合条件的单词的索引值
vector<int> findWord(string& pre);
//两个字典树的头指针
trieNode *forward_root, *backward_root;
addWord 和 findWord 函数的具体实现如下。其中查询函数因为要找到所有复合条件的单词,所以先用前缀定位到符合条件的结点,那么从这个结点开始向下延伸的所有路径都是符合条件的解,显而易见定位之后需要用到 DFS.
void addWord(trieNode *root, string word, int index) {
for (auto i : word) {
int idx = i - 'a';
if (root->children[idx] == nullptr)
root->children[idx] = new trieNode();
root = root->children[idx];
}
root->index = index;
root->isEnd = true;
}
vector<int> findWord(trieNode *root,string& pre) {
vector<int> ans;
for (auto i : pre) {
int idx = i - 'a';
if (root->children[idx] == nullptr)
return ans;
root = root->children[idx];
}
DFS(root, ans);
return ans;
}
void DFS(trieNode *root, vector<int>& ans) {
if (root == nullptr) return;
if (root->isEnd == true) ans.push_back(root->index);
for (auto i : root->children)
DFS(i, ans);
}
将单词依次添加进这两个字典树中
将单词word插入prefix树,将word逆转并插入suffix树。
void WordFilter(vector<string> words) {
forward_root = new trieNode();
backward_root = new trieNode();
for (int i = 0; i < words.size();i++) {
string w = words[i];
addWord(forward_root, w, i);
reverse(w.begin(), w.end());
addWord(backward_root, w, i);
}
}
根据前缀和后缀查找单词
先根据前缀在forward_root字典树中查找所有符合条件的单词索引,再根据后缀在backward_root字典树中查找,两个是完全镜像的过程,后缀反转之后变成前缀,所以步骤和前面一样。最后从这两组索引数组中找出权值最大的那个共同索引。
从两个数组中找出最大的共同元素,可以先把两个数组排序,然后从数组末尾找起,用两个索引指向末尾。比较两数组索引处的元素大小,若相同则该元素就是最大的共同元素,否则较大元素所在数组的末尾索引减一。
int f(string prefix, string suffix) {
vector<int> preIdx, sufIdx;
//根据前缀在第一个字典树中查找
preIdx = findWord(forward_root, prefix);
//将后缀反转,并在第二个字典树中查找
reverse(suffix.begin(), suffix.end());
sufIdx = findWord(backward_root, prefix);
//将两个索引值数组排序
sort(preIdx.begin(), preIdx.end());
sort(sufIdx.begin(), sufIdx.end());
//从数组末尾开始找起,若元素相同则就是最大共同元素
auto preItr = preIdx.rbegin(), sufItr = sufIdx.rbegin();
while (preItr != preIdx.rend() && sufItr != sufIdx.rend()) {
if (*preItr == *sufItr)
return *preItr;
else if (*preItr > *sufItr)
++preItr;
else
++sufItr;
}
return -1;
}