208. 实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。
解题
数据结构:字典树
(1)isend表示该节点是否为一个单词的终止节点;
(1)Trie每个节点保存一个字母表,保存字母树的单词前缀;
将每个字符保存在一个节点上;
class Trie {
private:
bool isEnd;
Trie* next[26];
public:
Trie() {
isEnd = false;
memset(next, 0, sizeof(next));
}
void insert(string word) {
Trie* node = this;
for (char c : word) {
if (node->next[c-'a'] == NULL) {
node->next[c-'a'] = new Trie();
}
node = node->next[c-'a'];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this;
for (char c : word) {
node = node->next[c - 'a'];
if (node == NULL) {
return false;
}
}
return node->isEnd;
}
bool startsWith(string prefix) {
Trie* node = this;
for (char c : prefix) {
node = node->next[c-'a'];
if (node == NULL) {
return false;
}
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
字典树模板:将单词保存在叶节点
class Trie {
private:
string word="";
vector<Trie *> next;
public:
/** Initialize your data structure here. */
Trie():next(26,0){} //构造函数
/** Inserts a word into the trie. */
void insert(string word) {
Trie *root=this;
for(char c:word){
if(!root->next[c-'a']) root->next[c-'a']=new Trie;
root=root->next[c-'a'];
}
root->word=word; //单词只放在最后面
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie * tmp=this;
for(char s:word)
if(tmp->next[s-'a'])
tmp=tmp->next[s-'a'];
else return false;
return tmp->word!="";
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie * tmp=this;
for(char s:prefix)
if(tmp->next[s-'a'])
tmp=tmp->next[s-'a'];
else return false;
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
212. 单词搜索 II
字典树+回溯
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例:
输入:
words = ["oath","pea","eat","rain"] and board =
[
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
输出: ["eat","oath"]
说明:
你可以假设所有输入都由小写字母 a-z 组成。
提示:
你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。
解题
利用字典树trie存放每一个单词;
添加条件: 当trie中search到当前单词,则加入结果数组;(找到档次后需酱trie对象中该单词的isend标为false,避免重复添加);
剪枝: 每次回溯时查看是否为前缀(startswith),否则可以提前退出(推出前记得取消visited和cur);
class Trie {
private:
bool isEnd;
Trie * next[26];
public:
/** Initialize your data structure here. */
Trie() {
isEnd=false;
memset(next,0,sizeof next);
}
/** Inserts a word into the trie. */
void insert(string word) {
Trie * tmp=this;
for(char s:word)
{
if(!tmp->next[s-'a'])
tmp->next[s-'a']=new Trie;
tmp=tmp->next[s-'a'];
}
tmp->isEnd=1;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie * tmp=this;
for(char s:word)
if(tmp->next[s-'a'])
tmp=tmp->next[s-'a'];
else return false;
if(tmp->isEnd){
tmp->isEnd=false; //找到后改为false,避免重复添加单词
return true;
}
return false;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie * tmp=this;
for(char s:prefix)
if(tmp->next[s-'a'])
tmp=tmp->next[s-'a'];
else return false;
return true;
}
};
class Solution {
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
m=board.size();
n=board[0].size();
visited.resize(m,vector<int>(n,0));
for(string s:words)
T.insert(s);
next={{0,1},{0,-1},{1,0},{-1,0}};
cur="";
string tmp="";
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(T.startsWith(tmp+board[i][j]))
dfs(board,i,j);
return res;
}
private:
Trie T;
vector<vector<int>> visited;
vector<string> res;
vector<vector<int>> next;
string cur;
int n,m;
void dfs(vector<vector<char>>& board,int x,int y){
if(visited[x][y]) return;
visited[x][y]=1;
cur+=board[x][y];
if(T.search(cur)) res.push_back(cur);
if(!T.startsWith(cur)) { //退回时要消除记录
visited[x][y]=0;
cur.erase(cur.end()-1);
return;
}
for(int i=0;i<4;i++)
{
int x1=x+next[i][0];
int y1=y+next[i][1];
if(x1>=0&&x1<m&&y1>=0&&y1<n){
dfs(board,x1,y1);
}
}
visited[x][y]=0;
cur.erase(cur.end()-1);
return;
}
};
优化代码:字典树改
字典树,单词直接添加到字典树末尾的word中;
结束条件: 沿着当前字典树能找到字符串,添加该字符串至结果;
省去visited列表: 修改board标记已遍历的坐标;
去重: 字符串添加至结果后,该位置的word清空,防止重复结果;
class TrieNode{
public:
string word = "";
vector<TrieNode*> nodes;
TrieNode():nodes(26, 0){}
};
class Solution {
int rows, cols;
vector<string> res;
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
rows = board.size();
cols = rows ? board[0].size():0;
if(rows==0 || cols==0) return res;
//建立字典树的模板
TrieNode* root = new TrieNode();
for(string word:words){
TrieNode *cur = root;
for(int i=0; i<word.size(); ++i){
int idx = word[i]-'a';
if(cur->nodes[idx]==0) cur->nodes[idx] = new TrieNode();
cur = cur->nodes[idx];
}
cur->word = word;
}
//DFS模板
for(int i=0; i<rows; ++i){
for(int j=0; j<cols; ++j){
dfs(board, root, i, j);
}
}
return res;
}
void dfs(vector<vector<char>>& board, TrieNode* root, int x, int y){
char c = board[x][y];
//递归边界
if(c=='.' || root->nodes[c-'a']==0) return;
root = root->nodes[c-'a'];
if(root->word!=""){
res.push_back(root->word);
root->word = "";
}
board[x][y] = '.';
if(x>0) dfs(board, root, x-1, y);
if(y>0) dfs(board, root, x, y-1);
if(x+1<rows) dfs(board, root, x+1, y);
if(y+1<cols) dfs(board, root, x, y+1);
board[x][y] = c;
}
};