1. 字典树的实现
208. 实现 Trie (前缀树)
请你实现 Trie 类:
Trie() :初始化前缀树对象。
void insert(String word): 向前缀树中插入字符串 word 。
boolean search(String word): 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) :如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例:
class Trie {
public:
class node
{
private:
vector<node*> next;
bool is_end;
public:
node():next(26, nullptr),is_end(false)
{
}
~node()
{
for(auto i : next)
{
if(i)
{
delete i;
i = nullptr;
}
}
}
node* insert(char v)
{
int idx = v-'a';
if(next[idx] == nullptr)
{
next[idx] = new node();
}
return next[idx];
}
node* find(char v)
{
return next [v-'a'];
}
void setend(){is_end = true;}
bool isend(){return is_end;}
};
Trie() {
root = new node();
}
~Trie() {
delete root;
}
void insert(string word) {
node* p = root;
for(int i = 0; i < word.size(); ++i)
{
p = p->insert(word[i]);
}
p->setend();
}
bool search(string word) {
node* p = root;
for(int i = 0; i < word.size();++i)
{
p = p->find(word[i]);
if(!p)
{
return false;
}
}
if(!p->isend())
{
return false;
}
return true;
}
bool startsWith(string prefix) {
node* p = root;
for(int i = 0; i < prefix.size();++i)
{
p = p->find(prefix[i]);
if(!p)
{
return false;
}
}
return true;
}
private:
node *root;
};
/**
* 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);
*/
提交结果:
效果不是很理想,想想应该怎么优化?
首先想到的是node节点定义的是26个字母的指针,实际上不需要那么多,用的时候再创建能否节省一些空间?
用map结构代替vector结构试一下:
class Trie {
public:
class node
{
private:
map<char, node*>next;
bool is_end;
public:
node():is_end(false)
{
}
~node()
{
for(map<char, node*>::iterator i = next.begin(); i != next.end(); ++i)
{
if(i->second)
{
delete i->second;
i->second = nullptr;
}
}
}
node* insert(char v)
{
if(next.count(v) == 0)
{
next[v] = new node();
}
return next[v];
}
node* find(char v)
{
if(next.count(v) == 1)
{
return next[v];
}
else
{
return nullptr;
}
}
void setend(){is_end = true;}
bool isend(){return is_end;}
};
Trie() {
root = new node();
}
~Trie() {
delete root;
}
void insert(string word) {
node* p = root;
for(int i = 0; i < word.size(); ++i)
{
p = p->insert(word[i]);
}
p->setend();
}
bool search(string word) {
node* p = root;
for(int i = 0; i < word.size();++i)
{
p = p->find(word[i]);
if(!p)
{
return false;
}
}
if(!p->isend())
{
return false;
}
return true;
}
bool startsWith(string prefix) {
node* p = root;
for(int i = 0; i < prefix.size();++i)
{
p = p->find(prefix[i]);
if(!p)
{
return false;
}
}
return true;
}
private:
node *root;
};
/**
* 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);
*/
提交结果:
可见在空间上节省了很多,但是执行用时提高不是很明显。
尝试把node节点操作挪到trie成员函数里吗,结果毫无变化。那么应该怎么提高执行效率?
最后通过题解的对比发现,还是用数组最快:
class Trie
{
public:
Trie * child[26];
bool isword;
/** Initialize your data structure here. */
Trie()
{
memset(child, NULL, sizeof(child));
isword = false;
}
/** Inserts a word into the trie. */
void insert(string word)
{
Trie * rt = this; /从根开始,相当于python3中的self
for (char w: word)
{
int ID = w - 'a';
if (rt->child[ID] == NULL) //path断了
rt->child[ID] = new Trie(); //新建
rt = rt->child[ID];
}
rt->isword = true;
}
/** Returns if the word is in the trie. */
bool search(string word)
{
Trie * rt = this;
for (char w: word)
{
int ID = w - 'a';
if (rt->child[ID] == 0)
return false;
rt = rt->child[ID];
}
return rt->isword == true;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix)
{
Trie * rt = this;
for (char p: prefix)
{
int ID = p - 'a';
if (rt->child[ID] == 0)
return false;
rt = rt->child[ID];
}
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);
*/
提交结果:
在空间和时间上效果都很好。
2. 添加和搜索单词
211. 添加与搜索单词 - 数据结构设计
请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 WordDictionary :
WordDictionary() 初始化词典对象
void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配
bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回 false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母
示例:
这道题就是在上面的基础上加入树的深度遍历:
class WordDictionary {
public:
WordDictionary* next[26];
bool is_end;
WordDictionary()
{
is_end = false;
memset(next,NULL,sizeof(next));
}
~WordDictionary()
{
for(int i= 0; i < 26; ++i)
{
if(next[i])
{
delete next[i];
}
}
}
void addWord(string word) {
WordDictionary *node = this;
for(char ch:word)
{
ch -= 'a';
if(node->next[ch] == NULL)
{
node->next[ch] = new WordDictionary();
}
node = node->next[ch];
}
node->is_end = true;
}
bool dfs(string word, int start,WordDictionary *node)
{
if(word.size() == start)
{
return node->is_end;
}
char ch = word[start];
if(ch == '.')
{
for(int i = 0; i < 26; ++i)
{
if(node->next[i] && dfs(word, start+1, node->next[i]))
{
return true;
}
}
}
else
{
ch -= 'a';
if(node->next[ch] && dfs(word, start+1, node->next[ch]))
{
return true;
}
}
return false;
}
bool search(string word) {
return dfs(word,0,this);
}
};
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary* obj = new WordDictionary();
* obj->addWord(word);
* bool param_2 = obj->search(word);
*/
提交结果:
用时有点久。对比其他答案,把树节点单独定义出来,执行效率会高一些:
struct Node
{
Node* children[26];
// 表示这里是一个单词的结束
bool isEnd;
Node()
{
memset(children, 0, sizeof(children));
isEnd = false;
}
};
class WordDictionary {
private:
Node* root;
public:
/** Initialize your data structure here. */
WordDictionary() {
root = new Node();
}
void addWord(string word) {
Node* curr = root;
for (char c : word)
{
int i = c - 'a';
if (curr->children[i] == nullptr)
{
curr->children[i] = new Node();
}
curr = curr->children[i];
}
// 最后一个字符则设置 isEnd
curr->isEnd = true;
}
bool dfs(string& word, int start, Node* curr)
{
// 已经遍历完,看是否是结束
if (start == word.size())
{
return curr->isEnd;
}
// 对于点,就是去找所有可能的下一个结点,然后递归
if (word[start] == '.')
{
for (int j = 0; j < 26; ++j)
{
if (curr->children[j] != nullptr && dfs(word, start+1, curr->children[j]))
{
return true;
}
}
return false;
}
else
{
// 其他情况就是找下一个结点
int i = word[start] - 'a';
if (curr->children[i] != nullptr)
{
return dfs(word, start+1, curr->children[i]);
}
else
{
return false;
}
}
}
bool search(string word) {
// 递归来查找
return dfs(word, 0, root);
}
};
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary* obj = new WordDictionary();
* obj->addWord(word);
* bool param_2 = obj->search(word);
*/
提交结果:
3. 单词搜索
212. 单词搜索 II
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
示例 2:
解题思路是字典树+回溯法
struct TrieNode {
string word;
unordered_map<char, TrieNode *> children;
TrieNode() {
this->word = "";
}
};
void insertTrie(TrieNode * root, const string & word) {
TrieNode * node = root;
for (auto c : word) {
if (!node->children.count(c)) {
node->children[c] = new TrieNode();
}
node = node->children[c];
}
node->word = word;
}
class Solution {
public:
int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
bool dfs(vector<vector<char>>& board, int x, int y, TrieNode * root, set<string> & res) {
char ch = board[x][y];
if (root == nullptr || !root->children.count(ch)) {
return false;
}
TrieNode * nxt = root->children[ch];
if (nxt->word.size() > 0) {
res.insert(nxt->word);
nxt->word = "";
}
if (!nxt->children.empty()) {
board[x][y] = '#';
for (int i = 0; i < 4; ++i) {
int nx = x + dirs[i][0];
int ny = y + dirs[i][1];
if (nx >= 0 && nx < board.size() && ny >= 0 && ny < board[0].size()) {
if (board[nx][ny] != '#') {
dfs(board, nx, ny, nxt,res);
}
}
}
board[x][y] = ch;
}
if (nxt->children.empty()) {
root->children.erase(ch);
}
return true;
}
vector<string> findWords(vector<vector<char>> & board, vector<string> & words) {
TrieNode * root = new TrieNode();
set<string> res;
vector<string> ans;
for (auto & word: words) {
insertTrie(root,word);
}
for (int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
dfs(board, i, j, root, res);
}
}
for (auto & word: res) {
ans.emplace_back(word);
}
return ans;
}
};