leetcode刷题 | 关于前缀树的题型总结

简介: 关于前缀树的题型总结


题目链接

208. 实现 Trie (前缀树) - 力扣(LeetCode)

648. 单词替换 - 力扣(LeetCode)

676. 实现一个魔法字典 - 力扣(LeetCode)

820. 单词的压缩编码 - 力扣(LeetCode)

677. 键值映射 - 力扣(LeetCode)

421. 数组中两个数的最大异或值 - 力扣(LeetCode)

实现 Trie (前缀树)

前缀树是一种数据结构,一般用于高效存储和检索字符串的键。

classTrie {

   TreeNoderoot;

   classTreeNode{

       TreeNode[] next;

       booleanisEnd;

       publicTreeNode(){

           // 子节点的指针数组,长度为26,可以包含所有的小写字母

           next=newTreeNode[26];

           // 表示是否为字符串的结尾

           this.isEnd=false;

       }

   }

   publicTrie() {

       // 初始化

       root=newTreeNode();

   }

   

   publicvoidinsert(Stringword) {

       TreeNodecur=root;

       // 将当前字符串插入到前缀树中

       for(charc : word.toCharArray()){

           // 如果不存在那么新建一个前缀树节点,进行插入

           // 如果存在那么指针移动到下一个节点,处理下一个字符

           if(cur.next[c-'a'] ==null) cur.next[c-'a'] =newTreeNode();

           cur=cur.next[c-'a'];

       }

       // 插入完毕标记到了结尾

       cur.isEnd=true;

   }

   

   publicbooleansearch(Stringword) {

       TreeNodecur=root;

       for(charc: word.toCharArray()){

           // 如果当前节点不存在,那么直接返回false,前缀树中没有这个字符串

           if(cur.next[c-'a'] ==null) returnfalse;

           cur=cur.next[c-'a'];// 继续处理下一个字符节点

       }

       returncur.isEnd; //返回是否结束,如果字符串没有结束那么也不包含该字符串

   }

 

   publicbooleanstartsWith(Stringprefix) {

       TreeNodecur=root;

       for(charc: prefix.toCharArray()){

           // 如果不存在,那么不存在该前缀

           if(cur.next[c-'a'] ==null) returnfalse;

           cur=cur.next[c-'a'];

       }

       returntrue;

   }

}

单词替换

使用前缀树构造一颗字典树,将所有的词根都放入到前缀树中,遍历句子中的每一个单词,对单词在字典中进行词根搜索,找到对应的词根,用找到的词根换当前单词

classSolution {

   TrieNoderoot=newTrieNode();

   classTrieNode{

       TrieNode[] next ;

       booleanisEnd;

       publicTrieNode(){

           next=newTrieNode[26];

           isEnd=false;

       }

   }

   // 将词根插入到字典树(前缀树)中

   publicvoidinsert(Stringword,TrieNoderoot){

       TrieNodenode=root;

       for(charc:word.toCharArray()){

           if(node.next[c-'a'] ==null) node.next[c-'a']=newTrieNode();

           node=node.next[c-'a'];

       }

       node.isEnd=true;

   }

    // 查找字符串中的词根

   publicbooleansearch(TrieNoderoot,Stringword){

       TrieNodenode=root;

       for(charc:word.toCharArray()){

           if(node.isEnd==true) break;

           if(node.next[c-'a'] ==null) returnfalse;

           node=node.next[c-'a'];

       }

       returntrue;

   }

   

   // 进行替换

   publicStringreplace(Stringword,TrieNoderoot){

       StringBuildersb=newStringBuilder();

       TrieNodenode=root;

       for(charc: word.toCharArray()){

           // 如果当前节点为末尾,跳出循环,返回当前找到的词根

           if(node.isEnd) break;

           // 处理下一个节点

           node=node.next[c-'a'];

           // 将当前词根加入到字符串中

           sb.append(c);

       }

       returnsb.toString();

   }

   publicStringreplaceWords(List<String>dictionary, Stringsentence) {

       // 插入词根

       for(Strings: dictionary) insert(s,root);

       String[] split=sentence.split(" ");

       for (inti=0; i<split.length; i++) {

           // 进行替换

           if (search(root,split[i])) split[i] =replace(split[i],root);

       }

       StringBuildersb=newStringBuilder();

       for(Strings: split){

           sb.append(s+" ");

       }

       sb.deleteCharAt(sb.length()-1);

       returnsb.toString();

   }

}

实现一个魔法字典

classMagicDictionary {

    classTrie {

       booleanisFinished;

       Trie[] child;

 

      publicTrie() {

          isFinished=false;

            child=newTrie[26];

      }

    }

   Trieroot;

 

   publicMagicDictionary() {

       root=newTrie();

   }

 

   publicvoidbuildDict(String[] dictionary) {

       for (Stringword : dictionary) {

           Triecur=root;

           for (inti=0; i<word.length(); ++i) {

               charch=word.charAt(i);

               intidx=ch-'a';

               if (cur.child[idx] ==null) {

                   cur.child[idx] =newTrie();

               }

               cur=cur.child[idx];

           }

           cur.isFinished=true;

       }

   }

 

   publicbooleansearch(StringsearchWord) {

       returndfs(searchWord, root, 0, false);

   }

 

   privatebooleandfs(StringsearchWord, Trienode, intpos, booleanmodified) {

       if (pos==searchWord.length()) {

           returnmodified&&node.isFinished;

       }

       intidx=searchWord.charAt(pos) -'a';

       if (node.child[idx] !=null) {

           if (dfs(searchWord, node.child[idx], pos+1, modified)) {

               returntrue;

           }

       }

       if (!modified) {

           for (inti=0; i<26; ++i) {

               if (i!=idx&&node.child[i] !=null) {

                   if (dfs(searchWord, node.child[i], pos+1, true)) {

                       returntrue;

                   }

               }

           }

       }

       returnfalse;

   }

}

 

我觉得遍历查找的方式就很好了,前缀树多少有点绕圈子b( ̄▽ ̄)d

classMagicDictionary {

   String[] words;

   /** Initialize your data structure here. */

   publicMagicDictionary() {

 

   }

   

   publicvoidbuildDict(String[] dictionary) {

       words=dictionary;

   }

   

   publicbooleansearch(StringsearchWord) {

       for(Strings: words){

           // 标记一个字符串中字符与词根中字符不同的个数

           intdiff=0;

           if(s.length() !=searchWord.length()) continue;

           for(inti=0;i<searchWord.length();i++){

               if(s.charAt(i) !=searchWord.charAt(i)) {

                   diff++;

                   // 大于1个那么直接跳出循环,返回false,不能够转换一次就和词根一样

                   if(diff>1) break;

               }

           }

           // 如果为1,那么可以转换

           if(diff==1) returntrue;

       }

       returnfalse;

   }

}

单词的压缩编码

保留所有不是其他单词后缀的单词

将所有的字符倒着插入到前缀树中,如果找到了

classSolution {

   publicintminimumLengthEncoding(String[] words) {

       TrieNoderoot=newTrieNode();

       HashMap<TrieNode,Integer>map=newHashMap();

       for(inti=0;i<words.length;i++){

           Strings=words[i];

           TrieNodenode=root;

           for(intj=s.length()-1;j>=0;j--){

               // 倒着插入前缀树

               // 得到当前节点

               node=node.get(s.charAt(j));

           }

           // 将节点和当前字符的下标存入到map中

           map.put(node,i);

       }

       intres=0;

       for(TrieNodenode : map.keySet()){

           // 遍历map

           // 如果当前节点的count = 0,说明为叶子节点,叶子结点就是不是后缀的单词

           // 取出map中的保存的下标,将所有不是后缀的单词长度相加

           // +1 是 +#

           if(node.count==0) res+=words[map.get(node)].length()+1;

       }    

       returnres;

   }

   classTrieNode{

       TrieNode[] children;

       intcount;

       publicTrieNode(){

           children=newTrieNode[26];

           count=0;

       }

       publicTrieNodeget(charc){

           if(children[c-'a'] ==null){

               // 前缀树中没有当前字符,创建前缀树节点

               children[c-'a'] =newTrieNode();

               count++; // 长度为1

           }

           // 如果前缀树中存在当前字符,那么该字符为某一个单词的后缀,直接返回当前节点,当前节点的值为1

           // 后续判断中会用到

           returnchildren[c-'a']; // 返回当前节点

       }

   }

}

键值映射

classMapSum {

   TrieNoderoot;

   HashMap<String,Integer>map;

 

   publicMapSum() {

       root=newTrieNode();

       map=newHashMap();

   }

   

   // 插入

   publicvoidinsert(Stringkey, intval) {

       // 当前值-map中包含该键的值,增量

       intd=val-map.getOrDefault(key,0);

       // 将key和val存入到map

       map.put(key,val);

       TrieNodecur=root;

       for(charc : key.toCharArray()){

           // 遍历key的字符

           // 字符节点为空,新建一个节点

           if(cur.next[c-'a'] ==null){

               cur.next[c-'a'] =newTrieNode();

           }

           // 继续处理下一个节点

           cur=cur.next[c-'a'];

           // 需要更新每个前缀对应的值

           // 把每一个节点都加增量,以便后续计算以某前缀开头所有key的和

           cur.val+=d;

       }

   }

   // 以某前缀开头所有key的和

   publicintsum(Stringprefix) {

       TrieNodecur=root;

       

       for(charc: prefix.toCharArray()){

           // 如果当前节点为空,那么没有前缀树中不存在该前缀,直接返回0

           if(cur.next[c-'a'] ==null) return0;

           cur=cur.next[c-'a'];

       }

       // 直接返回当前节点的值,就为和

       returncur.val;

   }

   classTrieNode{

       TrieNode[] next;

       intval;

       publicTrieNode(){

           next=newTrieNode[26];

           val=0;

       }

 

   }

}

这道题我也觉得HashMap最好用,直接使用哈希中封装好的函数👍

classMapSum {

   HashMap<String,Integer>map;

   /** Initialize your data structure here. */

   publicMapSum() {

        map=newHashMap();

   }

   

   publicvoidinsert(Stringkey, intval) {

       map.put(key,val);

   }

   

   publicintsum(Stringprefix) {

       intres=0;

       for(Strings:map.keySet()){

           if(s.startsWith(prefix)) res+=map.get(s);

       }

       returnres;

   }

}

数组中两个数的最大异或值

异或就是当位两个数的值不同才为1,所以需要尽量找到一个与当前位不同的数

先确定高位,再确定低位,才能保证最大 ,接着一位一位去确定这个数位的大小

classSolution {

  classTrieNode{

       TrieNode[] next;

       publicTrieNode(){

           next=newTrieNode[2];

       }

   }

   TrieNoderoot=newTrieNode();

   

   publicintfindMaximumXOR(int[] nums) {

       intmax=Integer.MIN_VALUE;

       for (intnum:nums){

           inster(num);

           max=Math.max(max,maxOR(num));

       }

       return  max;

   }

   publicvoidinster(intnum){

       TrieNodenode=root;

       // 倒着取出,先得到最低位

       for(inti=31;i>=0;i--){

           intd=num>>i&1; //取出第i位的数

           if (node.next[d] ==null) node.next[d] =newTrieNode();

           node=node.next[d];

       }

   }

   publicintmaxOR(intnum){

       TrieNodenode=root;

       intres=0;

       for (inti=31;i>=0;i--){

           intd=num>>i&1;

           // 由于异或要最大,则尽量走与 num 当前二进制位 d 相反的一位,

           // 如果相反的一位为空,则只好走相同的一位了。

           intopposite= (d==0)?  1 : 0; //为了让异或结果最大,选择不同的二进制进行异或

           if (node.next[opposite] !=null){

               res=res*2+opposite; //因为是二进制所以*2

               node=node.next[opposite];

           }else{

               //如果不存在这样的数,就先选择相同的

               res=res*2+d;

               node=node.next[d];

           }

       }

       return  num^res;

   }

   

}


相关文章
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
3月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
114 2
|
22天前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
16 1
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
3月前
|
算法 Python
【Leetcode刷题Python】 LeetCode 2038. 如果相邻两个颜色均相同则删除当前颜色
本文介绍了LeetCode 2038题的解法,题目要求在一个由'A'和'B'组成的字符串中,按照特定规则轮流删除颜色片段,判断Alice是否能够获胜,并提供了Python的实现代码。
51 3
|
3月前
|
Python
【Leetcode刷题Python】50. Pow(x, n)
本文介绍了LeetCode第50题"Pow(x, n)"的解法,题目要求实现计算x的n次幂的函数,文章提供了递归分治法的详细解析和Python实现代码。
27 1
|
3月前
|
Python
【Leetcode刷题Python】LeetCode 478. 在圆内随机生成点
本文介绍了LeetCode 478题的解法,题目要求在给定圆的半径和圆心位置的情况下实现在圆内均匀随机生成点的功能,并提供了Python的实现代码。
30 1
|
3月前
|
算法 Python
【Leetcode刷题Python】295. 数据流的中位数
本文介绍了一种使用Python实现的数据结构,用以支持数据流中添加整数并返回当前所有元素的中位数,通过排序列表来计算中位数。
25 1
|
3月前
|
算法 Python
【Leetcode刷题Python】73. 矩阵置零
本文介绍了LeetCode第73题的解法,题目要求在给定矩阵中将所有值为0的元素所在的行和列全部置为0,并提供了一种原地算法的Python实现。
32 0
【Leetcode刷题Python】73. 矩阵置零
|
3月前
|
Python
【Leetcode刷题Python】1467. 两个盒子中球的颜色数相同的概率
本文介绍了LeetCode第50题"Pow(x, n)"的解法,题目要求实现计算x的n次幂的函数,文章提供了递归分治法的详细解析和Python实现代码。
40 0