Leetcode 刷题指南-树(2)字典树 C++

作为 Leetcode 刷题指南-树 专题的第二章,本文主要介绍设计字符串搜索和存储结构的结构 字典树 以及相关的Leetcode题型。

关于字典树这一章节,都是套路模板题 啦!你们会看到每一道题都会有插入单词,搜索单词这两个标准模板,剩下的随机应变啦!

  1. 首先我们先定义作为字典树的节点的结构:
//字典树节点结构
    struct TreeNode
    {
        TreeNode*child[26];
        bool isword;
        TreeNode() {
        memset(child,NULL,sizeof(child));
        isword=false;
        }
    };

树的节点结构里面有两个变量:TreeNode*child[26]是一个TreeNode类型的指针数组,指针是否为空代表着该字母节点是否连着下个字母。布尔类型 isword用来判断单词是否结尾。例如我们要在字典树存储cat以及cdt这两个单词。Leetcode 刷题指南-树(2)字典树 C++_第1张图片
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;//标志着单词结束。
    }
  1. 接下来是判断一个单词是否存储在树里面,search函数的结束一般都是i==word.size()哦!
    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意味着并非单词结束。
    }

通过上面的三个步骤,我们就能实现字典树的基本功能。

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 构成的。
保证所有输入均为非空字符串。


==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. 词典中最长的单词

211. 添加与搜索单词 - 数据结构设计

设计一个支持以下两种操作的数据结构:

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));
    }
};

648. 单词替换

在英语中,我们有一个叫做 词根(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

676. 实现一个魔法字典

实现一个带有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);
    }
};

677. 键值映射

实现一个 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;
    }

};

720. 词典中最长的单词

给出一个字符串数组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

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

看到这里的,点赞三连呗。

你可能感兴趣的:(leetcode,树结构)