1. 字典树的实现
请你实现 Trie 类:
Trie() :初始化前缀树对象。
void insert(String word): 向前缀树中插入字符串 word 。
boolean search(String word): 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) :如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例:
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]解释
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
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. 添加和搜索单词
请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 WordDictionary :
WordDictionary() 初始化词典对象
void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配
bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回 false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母
示例:
输入:
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
输出:
[null,null,null,null,false,true,true,true]解释:
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // 返回 False
wordDictionary.search("bad"); // 返回 True
wordDictionary.search(".ad"); // 返回 True
wordDictionary.search("b.."); // 返回 True
这道题就是在上面的基础上加入树的深度遍历:
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. 单词搜索
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
示例 1:
输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出:["eat","oath"]
示例 2:
输入:board = [["a","b"],["c","d"]], words = ["abcb"] 输出:[]
解题思路是字典树+回溯法
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;
}
};
4. 回文对
给定一组 互不相同 的单词, 找出所有 不同 的索引对 (i, j)
,使得列表中的两个单词, words[i] + words[j]
,可拼接成回文串。
示例 1:
输入:words = ["abcd","dcba","lls","s","sssll"]
输出:[[0,1],[1,0],[3,2],[2,4]]
解释:可拼接成的回文串为 ["dcbaabcd","abcddcba","slls","llssssll"]
示例 2:
输入:words = ["bat","tab","cat"]
输出:[[0,1],[1,0]]
解释:可拼接成的回文串为 ["battab","tabbat"]
示例 3:
输入:words = ["a",""] 输出:[[0,1],[1,0]]
提示:
1 <= words.length <= 5000
0 <= words[i].length <= 300
words[i]
由小写英文字母组成
第一次解题,自然想到的是穷举法:
class Solution {
public:
bool checkstring(string s) // 检测是否回文
{
if(s.length() <= 0) return true;
int left = 0;
int right = s.length()-1;
while(left < right)
{
if(s[left] != s[right]) return false;
++left;
--right;
}
return true;
}
vector<vector<int>> palindromePairs(vector<string>& words) {
vector<vector<int>> ret;
map<string, bool> record;
for(int i = 0; i < words.size(); ++i)
{
for(int j = i+1; j < words.size(); ++j)
{
string s = words[i]+words[j];
if(record.count(s) == 1)
{
if(record[s])
{
ret.push_back({i,j});
}
}
else
{
if(checkstring(s))
{
ret.push_back({i,j});
record[s] = true;
}
else
{
record[s] = false;
}
}
s.clear();
s = words[j]+words[i];
if(record.count(s) == 1)
{
if(record[s])
{
ret.push_back({j,i});
}
}
else
{
if(checkstring(s))
{
ret.push_back({j,i});
record[s] = true;
}
else
{
record[s] = false;
}
}
}
}
return ret;
}
};
可想而知,提交结果是超时的。
应该怎么优化?
还是要用到字典树:
//字典树节点
class TrieNode
{
private:
bool isEnd;//单词结束标记
int index;//单词序号
vector<TrieNode*> children;//子节点
public:
//构造
TrieNode():index(-1),isEnd(false),children(26,nullptr){}
//析构
~TrieNode()
{
for(int i = 0;i < 26;i++)
{
if( children[i])
{
delete children[i];
children[i] = nullptr;
}
}
}
//对外接口
int getIndex() { return index;}
void setIndex( int i) { index = i;}
bool isWordEnd() { return isEnd;}
void SetEnd(){ isEnd = true ;}
//插入一个字符到子节点
TrieNode * insertNode(char c)
{
if( !( 'a' <= c <= 'z')) return nullptr;
int id = c-'a';
if( children[id] == nullptr)
{
children[id] = new TrieNode();
}
return children[id];
}
//在子节点中查找一个字符
TrieNode * getNode(char c)
{
if( !( 'a' <= c <= 'z')) return nullptr;
int id = c-'a';
return children[id] ;
}
};
//字典树
class Trie
{
private:
TrieNode * root;//根节点
public:
Trie():root(new TrieNode()){}
~Trie() { delete root;}
//插入一个单词及序号
void insert( string word,int index)
{
TrieNode * p = root;
for( int i = 0;i<word.size();i++)
{
p = p->insertNode(word[i]);
}
p->SetEnd();
p->setIndex(index);
}
//查找一个字符串
TrieNode *getNode(string word)
{
TrieNode * p = root;
for(int i = 0;i < word.size();i++ )
{
p = p->getNode(word[i]) ;
if( p == NULL ) return NULL;
}
return p;
}
//查找一个单词,返回序号
bool search(string word,int &index)
{
TrieNode * p = getNode(word);
if( p )
{
index = p->getIndex();
return p->isWordEnd();
}
return false;
}
};
class Solution {
public:
vector<vector<int>> palindromePairs(vector<string>& words) {
vector<vector<int>> res;
//构建字典树
Trie * trieTree = new Trie();
for(int i = 0;i < words.size();i++)
{
trieTree->insert(words[i],i);
}
for(int i = 0;i < words.size();i++)
{
for(int j = 0;j < words[i].size();j++ )
{
bool flag = check(words[i],0,j);
if(flag)//前半截是回文
{
string temp = words[i].substr(j+1);
reverse(temp.begin(),temp.end());
int index = -1;
if( trieTree->search(temp,index) )
{
if( i != index )
{
res.push_back({index,i});
if( temp == "")
{
res.push_back({i,index});
}
}
}
}
flag = check(words[i],j+1,words[i].size()-1);
if(flag)//后半截是回文
{
string temp = words[i].substr(0,j+1);
reverse(temp.begin(),temp.end());
int index = -1;
if( trieTree->search(temp,index) )
{
if( i != index )
res.push_back({i,index});
}
}
}
}
return res;
}
bool check(string &vec,int left,int right)
{
int i = left;
int j = right;
while(i <= j)
{
if( vec[i] != vec[j]) return false;
i++;
j--;
}
return true;
}
};