Leetcode_入门_字典树(前缀树)

Trie

1、实现 Trie (前缀树)

1)题目要求

实现一个 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 构成的。

2)我的解法

class Trie {
    class TrieNode{
        public boolean is_end;
        public TrieNode[] next;
        TrieNode(){
            is_end=false;
            next=new TrieNode[26];
        }
    }
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']==null)cur.next[word.charAt(i)-'a']=new TrieNode();
            cur=cur.next[word.charAt(i)-'a'];
        }
        cur.is_end=true;//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']!=null)cur=cur.next[word.charAt(i)-'a'];
            else return false;
        }
        if(!cur.is_end)return false;
        return true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode cur=root;
        for(int i=0;i<prefix.length();i++){
            if(cur.next[prefix.charAt(i)-'a']!=null)cur=cur.next[prefix.charAt(i)-'a'];
            else return false;
        }
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

3)其他解法

关于前缀树的介绍、用途点击下面链接查看

public class Trie {
    private boolean is_string=false;
    private Trie next[]=new Trie[26];

    public Trie(){}

    public void insert(String word){//插入单词
        Trie root=this;
        char w[]=word.toCharArray();
        for(int i=0;i<w.length;++i){
            if(root.next[w[i]-'a']==null)root.next[w[i]-'a']=new Trie();
            root=root.next[w[i]-'a'];
        }
        root.is_string=true;
    }

    public boolean search(String word){//查找单词
        Trie root=this;
        char w[]=word.toCharArray();
        for(int i=0;i<w.length;++i){
            if(root.next[w[i]-'a']==null)return false;
            root=root.next[w[i]-'a'];
        }
        return root.is_string;
    }
    
    public boolean startsWith(String prefix){//查找前缀
        Trie root=this;
        char p[]=prefix.toCharArray();
        for(int i=0;i<p.length;++i){
            if(root.next[p[i]-'a']==null)return false;
            root=root.next[p[i]-'a'];
        }
        return true;
    }
}

作者:LeetCode
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

class Trie {
    class TrieNode{
        public boolean is_end;
        public TrieNode[] next;
        TrieNode(){
            is_end=false;
            next=new TrieNode[26];
        }
    }
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']==null)cur.next[word.charAt(i)-'a']=new TrieNode();
            cur=cur.next[word.charAt(i)-'a'];
        }
        cur.is_end=true;//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']!=null)cur=cur.next[word.charAt(i)-'a'];
            else return false;
        }
        if(!cur.is_end)return false;
        return true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode cur=root;
        for(int i=0;i<prefix.length();i++){
            if(cur.next[prefix.charAt(i)-'a']!=null)cur=cur.next[prefix.charAt(i)-'a'];
            else return false;
        }
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

5)学到的东西

前缀树的概念及用法

插入前缀树时,//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束

2、 键值映射(677、Medium)

1)题目要求

实现一个 MapSum 类里的两个方法,insert 和 sum。

对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。

对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。

示例 1:

输入: insert(“apple”, 3), 输出: Null
输入: sum(“ap”), 输出: 3
输入: insert(“app”, 2), 输出: Null
输入: sum(“ap”), 输出: 5

2)我的解法

class MapSum {
    class Trie{
        boolean is_end=false;
        int val=0;
        Trie[] next=new Trie[26];
        public Trie(){}
    }
    Trie root;
    /** Initialize your data structure here. */
    public MapSum() {
        root=new Trie();
    }
    
    public void insert(String key, int val) {
        Trie cur=root;
        for(int i=0;i<key.length();i++){
            cur.val+=val;
            if(cur.next[key.charAt(i)-'a']==null)cur.next[key.charAt(i)-'a']=new Trie();
            cur=cur.next[key.charAt(i)-'a'];
        }
        cur.val+=val;
        if(cur.is_end){//当完全重复了时,进行覆盖
            cur=root;
            for(int i=0;i<key.length();i++){
                cur.val=val;
                cur=cur.next[key.charAt(i)-'a'];
            }
            cur.val=val;
        }
        cur.is_end=true;
    }
    
    public int sum(String prefix) {
        Trie cur=root;
        for(int i=0;i<prefix.length();i++){
            if(cur.next[prefix.charAt(i)-'a']==null)return 0;
            cur=cur.next[prefix.charAt(i)-'a'];
        }
        return cur.val;
    }
}

3)其他解法

class MapSum {
private:
    bool isEnd; // 是否为最后一个字母
    MapSum* next[26]; // 字母表
    int value; // 若为最后一个字母,其对应的值
    
    // 深度优先遍历算法
    int dfs(MapSum* root) { 
        if(!root) return 0; // 递归基:如果当前访问的MapSum为空,则直接返回0
        
        int res = 0;
        if(root->isEnd) res += root->value; // 若当前节点不为空且isEnd,则加上其值
        for(MapSum* cur : root->next) { // 再遍历当前节点的next数组中所有的MapSum
            res += dfs(cur);
        } 

        return res;
    }
public:
    /** Initialize your data structure here. */
    MapSum() {
        isEnd = false;
        memset(next, 0, sizeof(next));
        value = 0;
    }
    
    void insert(string key, int val) {
        MapSum* node = this;
        for(char ch : key) {
            if(node->next[ch - 'a'] == NULL) {
                node->next[ch - 'a'] = new MapSum();
            }
            node = node->next[ch - 'a'];
        }
        node->isEnd = true;
        node->value = val; // 相比较正常的前缀树,只是新增了一个value属性
    }
    
    int sum(string prefix) {
        MapSum* node = this;
        for(char ch : prefix) {
            if(node->next[ch - 'a'] == NULL) return 0;
            node = node->next[ch - 'a'];
        }
        return dfs(node);
    }
};


作者:xiao-xiao-he-miao
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

class MapSum {
    class Trie{
        boolean is_end=false;
        int val=0;
        Trie[] next=new Trie[26];
        public Trie(){}
    }
    Trie root;
    /** Initialize your data structure here. */
    public MapSum() {
        root=new Trie();
    }
    
    public void insert(String key, int val) {
        Trie cur=root;
        for(int i=0;i<key.length();i++){
            cur.val+=val;
            if(cur.next[key.charAt(i)-'a']==null)cur.next[key.charAt(i)-'a']=new Trie();
            cur=cur.next[key.charAt(i)-'a'];
        }
        cur.val+=val;
        if(cur.is_end){//当完全重复了时,进行覆盖
            cur=root;
            for(int i=0;i<key.length();i++){
                cur.val=val;
                cur=cur.next[key.charAt(i)-'a'];
            }
            cur.val=val;
        }
        cur.is_end=true;
    }
    
    public int sum(String prefix) {
        Trie cur=root;
        for(int i=0;i<prefix.length();i++){
            if(cur.next[prefix.charAt(i)-'a']==null)return 0;
            cur=cur.next[prefix.charAt(i)-'a'];
        }
        return cur.val;
    }
}

5)学到的东西

前缀树

3、单词搜索 II(212、Hard)

1)题目要求

给定一个二维网格 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(前缀树)。

2)我的解法

class TrieNode{
        public boolean is_end;
        public TrieNode[] next;
        public boolean tag;//判断该路径是否被访问过,search到达一次后置为true
        TrieNode(){
            is_end=false;
            next=new TrieNode[26];
            tag=false;
        }
    }
class Trie {
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']==null)cur.next[word.charAt(i)-'a']=new TrieNode();
            cur=cur.next[word.charAt(i)-'a'];
        }
        cur.is_end=true;//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']!=null)cur=cur.next[word.charAt(i)-'a'];
            else return false;
        }
        if(!cur.is_end)return false;
        //如果被访问过,返回false
        if(cur.tag)return false;
        cur.tag=true;
        //标记为被访问过
        return true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode cur=root;
        for(int i=0;i<prefix.length();i++){
            if(cur.next[prefix.charAt(i)-'a']!=null)cur=cur.next[prefix.charAt(i)-'a'];
            else return false;
        }
        return true;
    }
}
class Solution {
    Trie t;
    List<String> result=new ArrayList<>();
    boolean[][] vi;
    StringBuilder s=new StringBuilder();
    public void backpack(int i,int j,char[][] board){
        if(i<0||j<0||i>=board.length||j>=board[0].length||vi[i][j])return ;
        s.append(board[i][j]);
        vi[i][j]=true;
        if(!t.startsWith(s.toString())){
            vi[i][j]=false;
            s.deleteCharAt(s.length()-1);
            return ;
        }
        if(t.search(s.toString())){
            result.add(s.toString());//不要return,继续向下进行(bend、benda)

        }
        backpack(i-1,j,board);

        backpack(i+1,j,board);

        backpack(i,j-1,board);

        backpack(i,j+1,board);

        vi[i][j]=false;
        s.deleteCharAt(s.length()-1);
    }
    public List<String> findWords(char[][] board, String[] words) {
        t=new Trie();
        for(int i=0;i<words.length;i++){
            t.insert(words[i]);
        }
        vi=new boolean[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                backpack(i,j,board);
            }
        }
        result.sort((o1,o2)->o1.compareTo(o2));
        return result;
        
    }
}

3)其他解法

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class findWords_212 {
    public List<String> findWords(char[][] board, String[] words) {
        //构建字典树
        wordTrie myTrie=new wordTrie();
        trieNode root=myTrie.root;
        for(String s:words)
            myTrie.insert(s);

        //使用set防止重复
        Set<String> result =new HashSet<>();
        int m=board.length;
        int n=board[0].length;
        boolean [][]visited=new boolean[m][n];
        //遍历整个二维数组
        for(int i=0;i<board.length; i++){
            for (int j = 0; j < board [0].length; j++){
                find(board,visited,i,j,m,n,result,root);
            }
        }
        System.out.print(result);
        return new LinkedList<String>(result);
    }
    private void find(char [] [] board, boolean [][]visited,int i,int j,int m,int n,Set<String> result,trieNode cur){
        //边界以及是否已经访问判断
        if(i<0||i>=m||j<0||j>=n||visited[i][j])
            return;
        cur=cur.child[board[i][j]-'a'];
        visited[i][j]=true;
        if(cur==null)
        {
            //如果单词不匹配,回退
            visited[i][j]=false;
            return;
        }
        //找到单词加入
        if(cur.isLeaf)
        {
            result.add(cur.val);
            //找到单词后不能回退,因为可能是“ad” “addd”这样的单词得继续回溯
//            visited[i][j]=false;
//            return;
        }
        find(board,visited,i+1,j,m,n,result,cur);
        find(board,visited,i,j+1,m,n,result,cur);
        find(board,visited,i,j-1,m,n,result,cur);
        find(board,visited,i-1,j,m,n,result,cur);
        //最后要回退,因为下一个起点可能会用到上一个起点的字符
        visited[i][j]=false;
    }


}

//字典树
class wordTrie{
    public trieNode root=new trieNode();
    public void insert(String s){
        trieNode cur=root;
        for(char c:s.toCharArray()){
            if(cur.child[c-'a']==null){
                cur.child [c-'a'] = new trieNode();
                cur=cur.child[c-'a'];
            }else
                cur=cur.child [c-'a'];
        }
        cur.isLeaf=true;
        cur.val=s;
    }
}
//字典树结点
class trieNode{
    public String val;
    public trieNode[] child=new trieNode[26];
    public boolean isLeaf=false;

    trieNode(){

    }
}


作者:iwfly
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

1、每次递归带上一个TrieNode cur,避免每次都重新遍历

class TrieNode{
        public boolean is_end;
        public TrieNode[] next;
        public boolean tag;//判断该路径是否被访问过,search到达一次后置为true
        TrieNode(){
            is_end=false;
            next=new TrieNode[26];
            tag=false;
        }
    }
class Trie {
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']==null)cur.next[word.charAt(i)-'a']=new TrieNode();
            cur=cur.next[word.charAt(i)-'a'];
        }
        cur.is_end=true;//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束
    }
}
class Solution {
    Trie t;
    List<String> result=new ArrayList<>();
    boolean[][] vi;
    StringBuilder s=new StringBuilder();
    public void backpack(int i,int j,char[][] board,TrieNode cur){
        if(i<0||j<0||i>=board.length||j>=board[0].length||vi[i][j])return ;
        if(cur.next[board[i][j]-'a']==null)return;//相当于startwith
        cur=cur.next[board[i][j]-'a'];
        s.append(board[i][j]);
        vi[i][j]=true;
        if(cur.is_end&&!cur.tag){//相当于search
            cur.tag=true;
            result.add(s.toString());//不要return,继续向下进行(bend、benda)

        }
        backpack(i-1,j,board,cur);

        backpack(i+1,j,board,cur);

        backpack(i,j-1,board,cur);

        backpack(i,j+1,board,cur);

        vi[i][j]=false;
        s.deleteCharAt(s.length()-1);
    }
    public List<String> findWords(char[][] board, String[] words) {
        t=new Trie();
        for(int i=0;i<words.length;i++){
            t.insert(words[i]);
        }
        vi=new boolean[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                backpack(i,j,board,t.root);
            }
        }
        result.sort((o1,o2)->o1.compareTo(o2));
        return result;
        
    }
}

2、直接每个结点记录字符串,省去append时间

class TrieNode{
        public boolean is_end;
        public TrieNode[] next;
        public boolean tag;//判断该路径是否被访问过,search到达一次后置为true
        public String val;
        TrieNode(){
            is_end=false;
            next=new TrieNode[26];
            tag=false;
        }
    }
class Trie {
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode cur=root;
        for(int i=0;i<word.length();i++){
            if(cur.next[word.charAt(i)-'a']==null)cur.next[word.charAt(i)-'a']=new TrieNode();
            cur=cur.next[word.charAt(i)-'a'];
        }
        cur.is_end=true;//最后一个字母之后那个结点设为结束,而不是最后一个字母设为结束
        cur.val=word;
    }
    
   
}
class Solution {
    Trie t;
    List<String> result=new ArrayList<>();
    boolean[][] vi;
    StringBuilder s=new StringBuilder();
    public void backpack(int i,int j,char[][] board,TrieNode cur){
        if(i<0||j<0||i>=board.length||j>=board[0].length||vi[i][j])return ;
        if(cur.next[board[i][j]-'a']==null)return;//相当于startwith
        cur=cur.next[board[i][j]-'a'];

        vi[i][j]=true;
        if(cur.is_end&&!cur.tag){//相当于search
            cur.tag=true;
            result.add(cur.val);//不要return,继续向下进行(bend、benda)

        }
        backpack(i-1,j,board,cur);

        backpack(i+1,j,board,cur);

        backpack(i,j-1,board,cur);

        backpack(i,j+1,board,cur);

        vi[i][j]=false;
    }
    public List<String> findWords(char[][] board, String[] words) {
        t=new Trie();
        for(int i=0;i<words.length;i++){
            t.insert(words[i]);
        }
        vi=new boolean[board.length][board[0].length];
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                backpack(i,j,board,t.root);
            }
        }
        result.sort((o1,o2)->o1.compareTo(o2));
        return result;
        
    }
}

5)学到的东西

前缀树

回溯、剪枝

用标记tag(或HashSet)去重

每次递归带上一个TrieNode cur,避免每次都重新遍历

直接每个结点记录字符串,省去append时间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值