作为 Leetcode 刷题指南-树 专题的第二章,本文主要介绍设计字符串搜索和存储结构的结构 字典树 以及相关的Leetcode题型。
关于字典树这一章节,都是套路模板题 啦!你们会看到每一道题都会有插入单词,搜索单词这两个标准模板,剩下的随机应变啦!
//字典树节点结构
struct TreeNode
{
TreeNode*child[26];
bool isword;
TreeNode() {
memset(child,NULL,sizeof(child));
isword=false;
}
};
树的节点结构里面有两个变量:TreeNode*child[26]是一个TreeNode类型的指针数组,指针是否为空代表着该字母节点是否连着下个字母。布尔类型 isword用来判断单词是否结尾。例如我们要在字典树存储cat以及cdt这两个单词。
2. 上面展示了字典树是如何保存字母的,下面是讲单词加入树的代码实现:
void insert(string word) {
if(word.size()==0)
return;
TreeNode*node=root;
for(int i=0;ichild[ans]) node->child[ans]=new TreeNode();//例如一开始插入cat,child[2]要创建新节点
node=node->child[ans];//如果本身对应的child[ans]非空,例如我们加了cat后面加入cdt,处理c就不用再新开节点
}
node->isword=true;//标志着单词结束。
}
bool search(string word) {
if(word.size()==0)
return false;
TreeNode*node=root;//字典树的根节点
for(int i=0;ichild[ans])
node=node->child[ans];//例如cat单词,首先要判断child[2]是否为空,非空说明字母c保存在树中
else //此时指针更新为child[2],再判断新的节点中child[3](代表字母d)是否为空
return false;
}
if(node&&node->isword==true)//判断最后一个字母是否在树中相应的节点isword==1。声明为单词结束
return true; //例如树中保存着cattql,没有保存cat的话,当我们搜索cat到相对应的t节点,
return false; //isoword==0意味着并非单词结束。
}
通过上面的三个步骤,我们就能实现字典树的基本功能。
实现一个 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 构成的。
保证所有输入均为非空字符串。
==Method 1: ==
class Trie {
public:
/** Initialize your data structure here. */
struct TreeNode
{
TreeNode*child[26];
bool isword;
TreeNode() {
memset(child,NULL,sizeof(child));
isword=false;
}
};
TreeNode*root;
Trie(){
root=new TreeNode();//初始化树的时候就初始化节点
}
/** Inserts a word into the trie. */
void insert(string word) {
if(word.size()==0)
return;
TreeNode*node=root;
for(int i=0;ichild[ans]) node->child[ans]=new TreeNode();
node=node->child[ans];
}
node->isword=true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
if(word.size()==0)
return false;
TreeNode*node=root;
for(int i=0;ichild[ans])
node=node->child[ans];
else
return false;
}
if(node&&node->isword==true)
return true;
return false;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
if(prefix.size()==0)
return false;
TreeNode*node=root;
for(int i=0;ichild[ans]==NULL)
return false;
else
node=node->child[ans];
}
return true;
}
};
上面介绍就是字典树的基础,后面列出了一系列套路题: 211. 添加与搜索单词 - 数据结构设计 | 648. 单词替换 | 676. 实现一个魔法字典 | 677. 键值映射 | 720. 词典中最长的单词
设计一个支持以下两种操作的数据结构:
void addWord(word)
bool search(word)
search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。 . 可以表示任何一个字母。
示例:
addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true
说明:
你可以假设所有单词都是由小写字母 a-z 组成的。
==Method 1: ==
class WordDictionary {
public:
/** Initialize your data structure here. */
struct TreeNode{
TreeNode*child[26];
bool isword;
TreeNode(){
memset(child,NULL,sizeof(child));
isword=false;
}
};
TreeNode*root;
WordDictionary() {
root=new TreeNode();
}
/** Adds a word into the data structure. */
void addWord(string word) {
int n=word.size();
if(n==0)
return;
TreeNode*node=root;
for(int i=0;ichild[ans])
node->child[ans]=new TreeNode();
node=node->child[ans];
}
node->isword=true;
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
bool helper(string&word,TreeNode*node,int index)
{
int ans=word[index]-'a';
if(index==word.size()-1)//搜索到最后一个字母
{
if(word[index]=='.')//如果模式中最后一个字符为'.',那我们也要判断child[26]是否存在非空的,以及是不是作为单词结束
{
for(int j=0;j<26;j++)
{
if(node->child[j]&&node->child[j]->isword) return true;
}
return false;
}
else //判断child[26]对应的那一个字母,是否存在非空的,以及是不是作为单词结束
{
if(node->child[ans]&&node->child[ans]->isword) return true;
else return false;
}
}
if (word[index]=='.')//作为非最后一个字母且模式为'.',那么找到child[26]是否非空的,继续往下迭代
{
for(int j=0;j<26;j++)
{
TreeNode*tem=node->child[j];
if(tem)
return (helper(word,tem,index+1));
}
return false;
}
else//作为非最后一个字母且模式为正常字母,那么判断child[26]对应的那一个字母,是否存在非空的。
{
if(!node->child[ans]) return false;
else
{
node=node->child[ans];
if(helper(word,node,index+1)) return true;
}
}
return false;
}
bool search(string word) {
if(word.size()==0)
return false;
TreeNode*node=root;
return(helper(word,node,0));
}
};
在英语中,我们有一个叫做 词根(root)的概念,它可以跟着其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。
现在,给定一个由许多词根组成的词典和一个句子。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。
你需要输出替换之后的句子。
示例 1:
输入: dict(词典) = ["cat", "bat", "rat"]
sentence(句子) = "the cattle was rattled by the battery"
输出: "the cat was rat by the bat"
注:
输入只包含小写字母。
1 <= 字典单词数 <=1000
1 <= 句中词语数 <= 1000
1 <= 词根长度 <= 100
1 <= 句中词语长度 <= 1000
Method 1:
class Solution {
public:
struct TreeNode
{
bool isword;
TreeNode*child[26];
TreeNode()
{
isword=false; memset(child,NULL,sizeof(child));
}
};
TreeNode*root;
Solution(){root=new TreeNode();}
void insert(const vector& dic,TreeNode*root)
{
for(int i=0;ichild[ch1]) p->child[ch1]=new TreeNode();
p=p->child[ch1];
}
p->isword=true;
}
}
string search(string&str,TreeNode*node)
{
TreeNode*p=node;
int i=0;
for(i=0;ichild[ch1]) return str;//如果目前字母在树相对应层为空,说明没有词根,返回str
else
{//目前字母在对应层有值,且为结束,则立刻返回词根
if(p->child[ch1]->isword) return str.substr(0,i+1);
p=p->child[ch1];
}
}
return str;
}
string replaceWords(vector& dict, string sentence) {
insert(dict,root);
vector sentence_split;//用来存放分开的单词
string tem="";
sentence+=" ";//sentence后面加一个空格,方便切割
string result;
for(int i=0;i
实现一个带有buildDict, 以及 search方法的魔法字典。
对于buildDict方法,你将被给定一串不重复的单词来构建一个字典。
对于search方法,你将被给定一个单词,并且判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
示例 1:
Input: buildDict(["hello", "leetcode"]), Output: Null
Input: search("hello"), Output: False
Input: search("hhllo"), Output: True
Input: search("hell"), Output: False
Input: search("leetcoded"), Output: False
注意:
你可以假设所有输入都是小写字母 a-z。
为了便于竞赛,测试所用的数据量很小。你可以在竞赛结束后,考虑更高效的算法。
请记住重置MagicDictionary类中声明的类变量,因为静态/类变量会在多个测试用例中保留。 请参阅这里了解更多详情。
Method 1:
class MagicDictionary {
public:
/** Initialize your data structure here. */
struct TreeNode
{
bool isword; TreeNode*child[26];
TreeNode(){isword=false;
memset(child,NULL,sizeof(child));}
};
TreeNode*root;
MagicDictionary(){root=new TreeNode();}
/** Build a dictionary through a list of words */
void buildDict(vector dict) {
for(int i=0;ichild[ch1]) p->child[ch1]=new TreeNode();
p=p->child[ch1];
}
p->isword=true;
}
}
bool helper(string word,bool&flag,int level,TreeNode*p)//flag用来记录是否已经取代过一次
{
int ch1=word[level]-'a';
if(level==word.size()-1)//处理到最后一个字母
{
if(flag==false)
{//最后一个字母必须看看那些child[ch1]的,例如hello原本就在字典里有,就返回false。因为没变过字母
for(int i=0;i<26;i++)
{
if(i!=ch1&&p->child[i]&&p->child[i]->isword)
return true;
}
return false;
}
else
{//在最后一个字母前已经发生过一次取代,所以现在判断最后一个字母是否存在,且是否为结束
if(p->child[ch1]&&p->child[ch1]->isword) return true;
else return false;
}
}
else//处理的不是不是最后一个字母
{
if(flag==false)//没有发生过取代
{//假设在这层发生取代
for(int i=0;i<26;i++)
{
if(i!=ch1&&p->child[i])
{
flag=true;
if(helper(word,flag,level+1,p->child[i])) return true;
//回复原样,可以想想,剩下的25个child都不行,那么要看看不取代这个字母,能不能继续往下在,既然不取代,flag肯定为false
flag=false;
}
}
}
//如果到这里,一是原来的else部分,二是可能上面if里面运行完了,现在看看目前这个字母
if(p->child[ch1])
return helper(word,flag,level+1,p->child[ch1]);
else
return false;
}
return false;
}
/** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */
bool search(string word) {
bool flag=false;
return helper(word,flag,0,root);
}
};
实现一个 MapSum 类里的两个方法,insert 和 sum。
对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。
对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。
示例 1:
输入: insert("apple", 3), 输出: Null
输入: sum("ap"), 输出: 3
输入: insert("app", 2), 输出: Null
输入: sum("ap"), 输出: 5
Method 1:
class MapSum {
public:
/** Initialize your data structure here. */
struct TreeNode{
bool isword;
TreeNode*child[26];
int value;
TreeNode()
{
isword=false;
memset(child,NULL,sizeof(child));
value=0;
}
};
TreeNode*root;
MapSum() {
root=new TreeNode();
}
void insert(string key, int val) {
if(key.size()==0) return;
TreeNode*p=root;
for(int i=0;ichild[ch1]) p->child[ch1]=new TreeNode();
p=p->child[ch1];
}
p->value=val; p->isword=true;
}
void helper(TreeNode*node,int&sum)
{
if(node->isword) sum+=node->value;
for(int i=0;i<26;i++)
{
if(node->child[i])
helper(node->child[i],sum);//如果有node->child[i]说明可以继续往下
}
return;//有可能26个node->child[i]全部为NULL
}
int sum(string prefix) {
int n_pre=prefix.size();
TreeNode*p=root;
int result=0;
for(int i=0;ichild[ch1]) return 0;
p=p->child[ch1];
}//如果能结束这个循环,说明Prefix存在
helper(p,result);
return result;
}
};
给出一个字符串数组words组成的一本英语词典。从中找出最长的一个单词,该单词是由words词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。
若无答案,则返回空字符串。
示例 1:
输入:
words = ["w","wo","wor","worl", "world"]
输出: "world"
解释:
单词"world"可由"w", "wo", "wor", 和 "worl"添加一个字母组成。
示例 2:
输入:
words = ["a", "banana", "app", "appl", "ap", "apply", "apple"]
输出: "apple"
解释:
"apply"和"apple"都能由词典中的单词组成。但是"apple"得字典序小于"apply"。
注意:
所有输入的字符串都只包含小写字母。
words数组长度范围为[1,1000]。
words[i]的长度范围为[1,30]。
Method 1:
class Solution {
public:
struct TreeNode{
bool isword;
TreeNode*words[26];
TreeNode()
{
isword=false;
memset(words,NULL,sizeof(words));
}
};
TreeNode*root;
Solution()
{
root=new TreeNode();
}
void insert(string str,TreeNode*root)
{
TreeNode*p=root;
for(int i=0;iwords[ch1]) p->words[ch1]=new TreeNode();
p=p->words[ch1];
}
p->isword=true;
}
bool iscontain(string str,TreeNode*root)
{
TreeNode*p=root;
for(int i=0;iwords[ch1]||!(p->words[ch1]->isword)) return false;//假设搜索到的字母不在,返回错
else p=p->words[ch1];//假设搜索到了此刻的字母,准备找下一个
}
if(p->isword) return true;//最后一个字母对应是不是结束了
else return false;
}
string longestWord(vector& words) {
if(words.size()<1) return "";
int max_len=0;
string max_str="";
for(int i=0;imax_len)
{
if(iscontain(words[i],root))
{
max_len=words[i].size();
max_str=words[i];
}
}
if(words[i].size()==max_len)
{
if(iscontain(words[i],root))
{
if(words[i]
好了,看到这里都是基本的套路,如果还不过瘾,推荐 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"]
Method 1:
class Solution {
public:
const int xx[4]={-1,1,0,0};
const int yy[4]={0,0,1,-1};
struct TreeNode
{
TreeNode*child[26];
bool isword;
TreeNode()
{
for(int i=0;i<26;i++)
memset(child,NULL,sizeof(child));
isword=false;
cout<child[id]==NULL)
r->child[id]=new TreeNode();
r=r->child[id];
}
r->isword=true;
}
void helper(vector>& board,TreeNode*r,vector&result,int x,int y,string ss,vector>&used)
{ //如何判断结束,成功或者阻塞
int m=board.size(); int n=board[0].size();
int ans=board[x][y]-'a';
ss=ss+board[x][y];
if(!r->child[ans])
return;
if(r->child[ans]->isword==true)
{
result.push_back(ss);
r->child[ans]->isword=false;
}
used[x][y]=true;
for(int i=0;i<4;i++)
{
int new_x=x+xx[i],new_y=y+yy[i];
if(0<=new_x&&new_x<=m-1&&0<=new_y&&new_y<=n-1&&used[new_x][new_y]==false)
{
helper(board,r->child[ans],result,new_x,new_y,ss,used);
}
}
used[x][y]=false;
}
vector findWords(vector>& board, vector& words) {
vector result;
int m=board.size();
if(m==0)
return result;
int n=board[0].size();
if(n==0)
return result;
if(words.size()==0)
return result;
vector> used(m,vector(n,false));
string ss;
TreeNode*root=new TreeNode();
for(int i=0;i
看到这里的,点赞三连呗。