算法-----字典树

1.字典树简介

(1)用空间换时间的一种典型,如其名,字典,并暴力搜索查找,而是利用前缀去查找,这里的树是一颗拥有最大孩子数目的树。
(2)可以用在基础数据结构上自己加上一些需要的数据,比如以当前结点结尾的word值,以当前结点结尾的前缀数目,以当前结点结尾的字符长度,以当前结点结尾的word的数目(即words中该word重复的数目。)

基本数据结构

Trie[] children=new Trie[27]; //该结点的孩子
String word=""; //以当前结点结尾的word的值
boolean end=false; //当前结点是一个word的结尾吗?
int prCount=1; //以该结点结尾的前缀的数目
int lastCount=0; //以该结点结尾的words数目

package com.company;

public class Trie {
    Trie[] children=new Trie[27];  //该结点的孩子
    String word="";     //以当前结点结尾的word的值
    boolean end=false;  //当前结点是一个word的结尾吗?
    int prCount=1;      //以该结点结尾的前缀的数目
    int lastCount=0;    //以该结点结尾的words数目
    public Trie(){
        for(int i=0;i<children.length;i++){
            children[i]=null;
        }
    }

向结点root为根的树中插入word单词

 public void Insert(String word){
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null){
                root.children[ch-'a']=new Trie();
            }
            else{
                root.children[ch-'a'].prCount++;
            }
            root=root.children[ch-'a'];
        }
        root.lastCount++;
        root.word=word;
        root.end=true;
    }

建立以root为根节点的结点含有words的数

  public Trie createTrie(String[] words){
        Trie root=this;
        for(String word:words){
            root.Insert(word);
        }
        return root;
    }

寻找root树中以word为前缀的word的数目

 public int searchPreStr(String word){
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null) {
                return 0;
            }
            else{
                root=root.children[ch-'a'];
            }
        }
        return root.prCount;
    }

寻找root树中是否存在word单词

 public boolean searchWord(String word){
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null) {
                return false;
            }
            else{
                root=root.children[ch-'a'];
            }
        }
        if(root.word.equals(word)){
            return true;
        }
        else{
            return false;
        }
    }

寻找root树中word单词的数目

    public int searchWordCount(String word){
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null) {
                return 0;
            }
            else{
                root=root.children[ch-'a'];
            }
        }
        if(root.word.equals(word)){
            return root.lastCount;
        }
        else{
            return 0;
        }
    }
}
2.简单应用:
package com.company;

public class Main {
    public static void main(String[] args) {
        Trie root=new Trie();
        String[] words=new String[7];
        //cat,cash,app,apple,apply,ok
        words[0]=new String("cat");
        words[1]=new String("cash");
        words[2]=new String("app");
        words[3]=new String("apple");
        words[4]=new String("apply");
        words[5]=new String("ok");
        words[6]=new String("apply");
        root.createTrie(words);
        System.out.println("cash is appeal? "+root.searchWord("cash"));
        System.out.println("ap 前缀 appeal times:"+root.searchPreStr("ap"));
        System.out.println("apply appeal times:"+root.searchWordCount("apply"));
    }
}

在这里插入图片描述

3.力扣刷题
(1)720. 词典中最长的单词

给出一个字符串数组words组成的一本英语词典。从中找出最长的一个单词,该单词是由words词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。

若无答案,则返回空字符串。

在这里插入图片描述
敲黑板:这里就不是简简单单的直接遍历什么的了,需要根据题目来,基础建立字典树都不变,但是遍历这里需要注意,题目中明确了我最终获得的这个必须是其他的word加1个得到的,也就是说我这一路走过来,必须经过的都是别人的end == true,所以一旦是false那就是错的!!!

class Solution {

    public String longestWord(String[] words) {
        Trie root=new Trie();
        root.createTrie(words);
        dfs(root,0);
        return ans;
    }
    class Trie{
        public boolean end=false;
        public String word="";
        public Trie[] children=new Trie[26];
        public int preCount=1;
        public int lastCount=0;

        public Trie(){
            for(int i=0;i<children.length;i++){
                children[i]=null;
            }
        }

        public void Insert(String word){
            Trie root=this;
            for(char ch:word.toCharArray()){
                if(root.children[ch-'a']==null){
                    root.children[ch-'a']=new Trie();
                }
                else{
                    root.children[ch-'a'].preCount++;
                }
                root=root.children[ch-'a'];
            }
            root.lastCount++;
            root.word=word;
            root.end=true;
        }

        public Trie createTrie(String[] words){
            Trie root=this;
            for(String word:words){
                root.Insert(word);
            }
            return root;
        }
    }

    public int maxLength=0;
    public String ans="";
    
    public void dfs(Trie root,int depth){
        if(depth>0 && !root.end){
            return ;
        }
        if(depth>maxLength){
            maxLength=depth;
            ans=root.word;
        }
        for(Trie child:root.children){
            if(child!=null){
                dfs(child,depth+1);
            }
        }
    }
}

在这里插入图片描述

(2)421. 数组中两个数的最大异或值
给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,
其中 0 ≤ i ≤ j < n 。

进阶:你可以在 O(n) 的时间解决这个问题吗?
示例 1:
输入:nums = [3,10,5,25,2,8]
输出:28
解释:最大运算结果是 5 XOR 25 = 28.
class Solution {

    class Trie{
    public Trie[] children=new Trie[2];
    public int num;
    boolean end=false;
    public Trie(){
        for(int i=0;i<children.length;i++){
            children[i]=null;
        }
    }

    public void insert(int num){
        Trie root=this;
        for(int i=30;i>=0;i--){
            int index=(num>>i)&1;
            if(root.children[index]==null){
                root.children[index]=new Trie();
            }
            root=root.children[index];
        }
        root.num=num;
        root.end=true;
    }

    public int findMax(int num){
        int ans=0;
        Trie root=this;
        for(int i=30;i>=0;i--){
            int index=(num>>i)&1;
            index^=1;
            if(root.children[index]!=null){
                ans|=1<<i;
                root=root.children[index];
            }
            else{
                index^=1;
                root=root.children[index];
            }
        }
        return ans;
    }
  }

    public int findMaximumXOR(int[] nums) {
        int max=0;
        Trie root=new Trie();
        for(int num:nums){
            root.insert(num);
            max=Math.max(max,root.findMax(num));
        }
        return max;
    }
}
(3)208. 实现 Trie (前缀树)

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

class Trie {
    boolean end;
    String word;
    Trie[] children=new Trie[26];
    /** Initialize your data structure here. */
    public Trie() {
        end=false;
        word="";
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null){
                root.children[ch-'a']=new Trie();
            }
            root=root.children[ch-'a'];
        }
        root.end=true;
        root.word=word;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Trie root=this;
        for(char ch:word.toCharArray()){
            if(root.children[ch-'a']==null){
                return false;
            }
            root=root.children[ch-'a'];
        }
        if(root.word.equals(word)){
            return true;
        }
        else{
            return false;
        }
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        Trie root=this;
        for(char ch:prefix.toCharArray()){
            if(root.children[ch-'a']==null){
                return false;
            }
            root=root.children[ch-'a'];
        }
        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);
 */
(4)1638. 统计只差一个字符的子串数目
给你两个字符串 s 和 t ,请你找出 s 中的非空子串的数目,这些子串满足替换 一个不同字符 以后,是 t 串的子串。换言之,请你找到 s 和 t 串中 恰好 只有一个字符不同的子字符串对的数目。
比方说, "computer" 和 "computation" 加粗部分只有一个字符不同: 'e'/'a' ,所以这一对子字符串会给答案加 1 。

请你返回满足上述条件的不同子字符串对数目。

一个 子字符串 是一个字符串中连续的字符。
示例 1:

输入:s = "aba", t = "baba"
输出:6
解释:以下为只相差 1 个字符的 s 和 t 串的子字符串对:
("aba", "baba")
("aba", "baba")
("aba", "baba")
("aba", "baba")
("aba", "baba")
("aba", "baba")
加粗部分分别表示 s 和 t 串选出来的子字符串。

class Solution {
    class Trie{
        Trie[] children=new Trie[26];
        int endCount=0;
        public Trie(){}
        public void Insert(String word){
            Trie root=this;
            for(char ch:word.toCharArray()){
                if(root.children[ch-'a']==null){
                    root.children[ch-'a']=new Trie();
                }
                root=root.children[ch-'a'];
            }
            root.endCount++;
        }

        public int compire(String word){
            Trie root=this;
            for(char ch:word.toCharArray()){
                if(root.children[ch-'a']==null){
                    return 0;
                }
                root=root.children[ch-'a'];
            }
            return root.endCount;
        }
    }

    public int countSubstrings(String s, String t) {
        if(s.length()>t.length()){
            String temp=new String(s);
            s=new String(t);
            t=new String(temp);
        }
        Trie root=new Trie();
        for(int i=0;i<t.length();i++){
            for(int j=i+1;j<=t.length();j++){
                root.Insert(t.substring(i,j));
            }
        }
        int count=0;
        for(int i=0;i<s.length();i++){
            for(int j=i+1;j<=s.length();j++){
                String sub=s.substring(i,j);
                for(int k=0;k<sub.length();k++){
                    char[] subArray=sub.toCharArray();
                    for(char changeCh='a';changeCh<='z';changeCh++){
                        if(sub.charAt(k)!=changeCh){
                            subArray[k]=changeCh;
                            count+=root.compire(String.copyValueOf(subArray));
                        }
                    }
                }
            }
        }
        return count;
    }
}
法二:
class Solution {
    public int countSubstrings(String s, String t) {
        int count=0;
        for(int i=0;i<s.length();i++){
            for(int j=i+1;j<=s.length();j++){
                String sstr=s.substring(i,j);
                for(int k=0;k<t.length()-sstr.length()+1;k++){
                    String tstr=t.substring(k,k+sstr.length());
                    count+=match(sstr,tstr);
                }
            }
        }
        return count;
    }

    public int match(String s,String t){
        int dif=0;
        for(int i=0;i<s.length();i++){
            if(s.charAt(i)!=t.charAt(i)){
                dif++;
            }
            if(dif>1){
                return 0;
            }
        }
        if(dif==1){
            return 1;
        }
        else{
            return 0;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值