前綴樹的操作

前綴樹是N叉樹的一種形式,常用於存儲字符串,樹中每一個節點表示一個字符。
前綴樹重要的存在價值是搜索速度,典型的利用空間換時間,時間複雜度爲O(n),n是樹的深度。

Trie

上圖中存儲了四個單詞:am、bad、be、so,位於葉子節點,葉子節點一定爲詞,但詞不一定位於葉子節點。除了存儲詞的節點外,其餘節點稱爲前綴。如ba,在樹中並不是一個詞,但他是bad詞的前綴,前綴的重要作用就是減少存儲空間,具有相同前綴的不同單詞,只需存儲差異部分,大大減少了空間的存儲。

我所喜歡的數據結構:

public class TrieNode {
    public boolean isWord;
    public HashMap<Character, TrieNode> nexts = new HashMap<Character, TrieNode>();
}

Trie常見的操作:

插入

    private void insert(String word, TrieNode root) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            if (node.nexts.get(word.charAt(i)) == null) {//沒有前綴
                node.nexts.put(word.charAt(i), new TrieNode());
            }
            node = node.nexts.get(word.charAt(i));//切記切換到子節點,否則全部存在了一層
        }
        node.isWord = true;//標記單詞存儲結束,其餘節點爲前綴
    }

查找單詞

    private boolean search(String word, TrieNode root) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            if (node.nexts.get(word.charAt(i)) == null) {
                return false;//中途查找失敗,就失敗
            }
            node = node.nexts.get(word.charAt(i));//切換到下一層
        }
        return node.isWord;//查找到了但不一定是單詞,有可能是其他單詞的前綴,因此需要判斷
    }

查找前綴

    public boolean startsWith(String prefix) {
        char[] chars=prefix.toCharArray();
        TrieNode node=root;
        for(int i=0;i<chars.length;i++){
            if(node.nexts.get(chars[i])==null){
                return false;//沒查完就到頂了,失敗
            }else{
                node=node.nexts.get(chars[i]);//查找下一層
            }
        }
        return true;//查找到了一定是前綴,與查找單詞不同
    }

Map Sum Pairs

https://leetcode.com/explore/learn/card/trie/148/practical-application-i/1058/

這個題目實在是翻譯不出來啊!題意:插入多個單詞(apple、app)給每一個詞賦值apple=3、app=2,當輸入前綴ap時,返回以ap爲前綴的所有單詞值的和。

Example:
Input: insert(“apple”, 3), Output: Null
Input: sum(“ap”), Output: 3
Input: insert(“app”, 2), Output: Null
Input: sum(“ap”), Output: 5

    public int sum(String prefix) {
        char[] chars=prefix.toCharArray();
        TrieNode node=root;
        for(int i=0;i<chars.length;i++){
            if(node.nexts.get(chars[i])==null){//前綴找到一半就夭折了
                return 0;
            }else{
                node=node.nexts.get(chars[i]);//去下一層尋找
            }
        }

        return value(node);//找到了前綴的最後一個節點
    }

    private int value(TrieNode root){
        TrieNode node=root;
        int sum=0;
        if(node==null){
            return 0;
        }
        if(node.isWord){//此節點本身就是單詞,求和
            sum+=node.value;
        }
        if(node.nexts.keySet().size()>0){//節點還有子節點
            for(Character key :node.nexts.keySet()){
                sum+=value(node.nexts.get(key));//遞歸求和
            }
        }
        return sum;
    }

替換

一段文字內替換所有以
Trie中存儲的單詞
的單詞

Example
Input: dict = [“cat”, “bat”, “rat”]
sentence = “the cattle was rattled by the battery”
Output: “the cat was rat by the bat”

步驟:
- 構造Trie
- 將句子拆分成單詞
- 將拆分的單詞一個個的去Trie中查找,找到便替換

    private String replaceWords(TrieNode root,String[] words){
        StringBuilder stringBuilder=new StringBuilder();
        for(int i=0;i<words.length;i++){
            stringBuilder.append(replaceWords(root,words[i])).append(" ");//將拆分的單詞一個個的去Trie中查找,找到便替換
        }
        stringBuilder.deleteCharAt(stringBuilder.length()-1);
        return stringBuilder.toString();
    }

    private String replaceWords(TrieNode root,String word){
        TrieNode node=root;
        StringBuilder stringBuilder=new StringBuilder();
        for(int i=0;i<word.length();i++){
            if(node.nexts.get(word.charAt(i))==null){//沒有此單詞的前綴
                return word;
            }else{
                stringBuilder.append(word.charAt(i));
                if(node.nexts.get(word.charAt(i)).isWord){//找到了單詞的替換
                    return stringBuilder.toString();
                }else{
                    node=node.nexts.get(word.charAt(i));//查找下一層
                }
            }
        }
        return stringBuilder.toString();
    }

模糊查找

addWord(“bad”)
addWord(“dad”)
addWord(“mad”)
search(“pad”) -> false
search(“bad”) -> true
search(“.ad”) -> true
search(“b..”) -> true

步驟:
- 構建字典
- 每個字符在Trie中查找,碰到模糊詞彙使用遞歸

    public boolean search(String word) {
        return search(word,root);
    }

    public boolean search(String word,TrieNode root){
        if(word.length()==0){//每個字符都找完了,看最後一個節點是不是單詞
            return root.isWord;
        }

        char c=word.charAt(0);
        if(c!='.'){//非模糊
            if(root.nexts.get(c)==null){//字符不匹配查找失敗
                return false;
            }else{//字符匹配,遞歸看子節點
                return search(word.substring(1),root.nexts.get(c));
            }
        }else{//模糊
            for(char ch :root.nexts.keySet()){//跳過模糊字符,查找下一字符
                if(search(word.substring(1),root.nexts.get(ch)))//注意成功返回,失敗一次不代表全部失敗
                    return true;
            }
            return false;//全部失敗
        }

    }

求最大異或值

在給定的數組中兩兩項異或,找出最大的異或值。

性質:
有 a ^ b = x
則 a ^ x = b , b ^ x = a

一個數的大小如何判斷?從高位向低位走,先遇到不爲0的數最大(1000 、0100),若高位相同繼續向低位走(1000 、 1100)。

思路:
- 將整數轉成二進制存入Trie
- 由於求最大值,所以從高位向低位存儲
- 循環數組
- 假設項爲a,存在於Trie中最大的異或值爲x,a、Trie已知
- 若想x最大,則x的每一位都爲1,但不可能
- 用a的每一位去異或1(性質),求得b的這一位,若b的這一位在Trie中,則最大的x這一位確實存在,否則下一位
- 所有位都走完,求得a在Trie中的最大異或值。
- 循環數組完畢,找出最大的異或值

由於存儲的節點只有0、1所以修改TrieNode結構

class TrieNode{
    TrieNode[] children;

    public TrieNode(){
        children = new TrieNode[2];
    }
}

構造Trie

private void buildTrie(int[] nums,TrieNode root){
        for(int num:nums){
            TrieNode node=root;
            for(int i=31;i>=0;i--){
                int bit=(num >> i)&1;//與1是爲了只求一位
                if(node.children[bit]==null){
                    node.children[bit]=new TrieNode();
                }
                node=node.children[bit];
            }
        }
}

遍歷查找最大異或值

public int findMaximumXOR(int[] nums,TrieNode root) {
        int max=0;
        for(int num :nums){
            TrieNode node=root;
            int res=0;
            for(int i=31;i>=0;i--){
                int bit=(num >> i)&1;

                if(node.children[bit^1]!=null){//找到b的這一位
                    res+=1<<i;//累加x的這一位
                    node=node.children[bit^1];//去b的下一位找最大值
                }else{//沒找到b的這一位,如思路中的x的每一位不可能都爲1
                    node=node.children[bit];
                }
//由於是從高位開始查找的,所以先找到的1,一定是最大的
            }
            max=Math.max(res,max);
        }
        return max;
}

矩陣中查找單詞

給定矩陣,判斷輸入的單詞是否在矩陣中。

[
[‘o’,’a’,’a’,’n’],
[‘e’,’t’,’a’,’e’],
[‘i’,’h’,’k’,’r’],
[‘i’,’f’,’l’,’v’]
]
矩陣中字符可以橫向縱向組合成單詞

思路:
- 構建要查找單詞的Trie
- 遍歷矩陣中的每一項
- 讓每一項上下左右構成單詞後去Trie中查找(遞歸)

    public List<String> findWords(char[][] board, String[] words) {
        Trie trie=new Trie();
        for(String s: words){
            trie.insert(s);
        }

        int y=board.length;
        int x=board[0].length;

        boolean[][] visited=new boolean[y][x];//記錄是否走過此路

        for(int i=0;i<x;i++){
            for(int j=0;j<y;j++){//遍歷所有的點,這裏還不是遞歸
                dfs(board,visited,"",i,j,trie);
            }
        }
        return new ArrayList<String>(res);
    }

    public void dfs(char[][] board,boolean[][] visited,String str,int x,int y,Trie trie){
        if(x<0||x>=board[0].length||y<0||y>=board.length) return;//所要查詢的位置越界

        if(visited[y][x])return;//已經走過該路

        str+=board[y][x];//新字符串

        if(!trie.startWith(str)) return;//如果沒有此前綴,不必浪費時間

        if(trie.search(str)){//在矩陣中找到該詞,加入結果
            res.add(str);
        }

        visited[y][x]=true;//記錄當前已走
        dfs(board,visited,str,x-1,y,trie);//橫向查找
        dfs(board,visited,str,x+1,y,trie);
        dfs(board,visited,str,x,y-1,trie);//縱向查找
        dfs(board,visited,str,x,y+1,trie);
        visited[y][x]=false;//真個矩陣走完後,重置狀態爲下一次做準備
    }

迴文

在給出的單詞組中,找出可以組成迴文的兩個單詞組。
LeetCode

Example 1:
Given words = [“bat”, “tab”, “cat”]
Return [[0, 1], [1, 0]]
The palindromes are [“battab”, “tabbat”]
Example 2:
Given words = [“abcd”, “dcba”, “lls”, “s”, “sssll”]
Return [[0, 1], [1, 0], [3, 2], [2, 4]]
The palindromes are [“dcbaabcd”, “abcddcba”, “slls”, “llssssll”]

 class TrieNode {
    HashMap<Character,TrieNode> children;
    int index;
    List<Integer> list;

    public TrieNode(){
        children=new HashMap<Character,TrieNode>();
        index=-1;
        list=new ArrayList<Integer>();
    }
}

    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> res=new ArrayList<List<Integer>>();
        TrieNode root=new TrieNode();
        for(int i=0;i<words.length;i++) addWord(root,words[i],i);
        for(int i=0;i<words.length;i++) search(words[i],i,root,res);
        return res;
    }

    private void addWord(TrieNode root,String word,int index){
        TrieNode node=root;
        for(int i=word.length()-1;i>=0;i--){//倒序插入,正序查找
            if(node.children.get(word.charAt(i))==null){
                node.children.put(word.charAt(i),new TrieNode());
            }
            if(isPalindrome(word,0,i)) node.list.add(index);//單詞內部迴文,node不僅僅屬於此word,所以要添加到list
            node=node.children.get(word.charAt(i));
        }
        node.list.add(index);//此單詞結束,不代表到達葉子節點
        node.index=index;//記錄此節點爲單詞
    }

    private void search(String word,int index,TrieNode root,List<List<Integer>> res){
        for(int i=0;i<word.length();i++){
            if(root.index>=0&&root.index!=index&&isPalindrome(word,i,word.length()-1)){
//倒序插入,正序查找,找到的節點爲單詞且不是自己,證明到此節點root單詞與單詞前半部分迴文,如果單詞後半部分迴文,則兩單詞一定迴文
                res.add(Arrays.asList(index,root.index));
            }
            root=root.children.get(word.charAt(i));
            if(root==null){//查找結束
                return;
            }
        }

        for(int i:root.list){//最後一個節點一定是單詞,同root.index>=0
            if(index==i) continue;//去除自己,同root.index!=index
            res.add(Arrays.asList(index,i));//由於是最後一個節點,所以沒有後半部分
        }
//單詞A、B,迴文不一定AB迴文且BA迴文,只要其中一項即可,勿陷入盲區

    }

    private boolean isPalindrome(String word,int start,int end){
        while(start<end){
            if(word.charAt(start++)!=word.charAt(end--)) return false;
        }
        return true;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章