字典树/前缀树(Trie)

直接有道题教板子了。

1. LC 208 实现Trie(前缀树)

1.1. 数组写法

class Trie {
    // 二维数组写法
    static int size = (int) 1e5;
    int index;
    int[][] trie;
    int[] count;
    public Trie() {
        index = 0;
        trie = new int[size][26];
        count = new int[size];
    }

    public void insert(String word) {
        int p = 0;
        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if(trie[p][c]==0) trie[p][c] = ++index; // 不能是index++,因为不可能出现在同一行,要换行了
            p = trie[p][c];
        }
        count[p]++; // 以该索引结尾的字符串数量赠一
    }

    public boolean search(String word) {
        int p = 0;
        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if(trie[p][c]==0) return false;
            p = trie[p][c];
        }
        return count[p]>0;
    }

    public boolean startsWith(String prefix) {
        int p = 0;
        for (int i = 0; i < prefix.length(); i++) {
            int c = prefix.charAt(i) - 'a';
            if(trie[p][c]==0) return false;
            p = trie[p][c];
        }
        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);
 */

我个人不喜欢这种。没有树的感觉。

1.2. 节点写法

class Trie {
    // 节点写法
    static class TrieNode{
        boolean end; // this node is the end of some inserted string?
        TrieNode[] tns = new TrieNode[26]; // the successor node has 26 choices since the character set of a string has only 26 elements
    }
    TrieNode root;
    public Trie() {
        root = new TrieNode();
    }

    public void insert(String word) {
        TrieNode p = root;
        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if(p.tns[c]==null) p.tns[c] = new TrieNode();
            p = p.tns[c];
        }
        p.end = true;
    }

    public boolean search(String word) {
        TrieNode p = root;
        for (int i = 0; i < word.length(); i++) {
            int c = word.charAt(i) - 'a';
            if(p.tns[c]==null) return false;
            p = p.tns[c];
        }
        return p.end;
    }

    public boolean startsWith(String prefix) {
        TrieNode p = root;
        for (int i = 0; i < prefix.length(); i++) {
            int c = prefix.charAt(i) - 'a';
            if(p.tns[c]==null) return false;
            p = p.tns[c];
        }
        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);
 */

对味了。

2. LC 3045 统计前后缀下标对Ⅱ

Trie而不板的一道题,实际上很有思维量:如何判断既是前缀也是后缀?

把一个字符串转换为字符对列表,其中pair(i) = ( s(i) , s(n-i-1) ),下标从0开始算。例如:

str1 = abc
str2 = abczabc
转换为
p1 = (a,c) (b,b) (c,a)
p2 = (a,c) (b,b) (c,a) (z,z) (a,c) (b,b) (c,a)

那么如果p1的所有对是p2的所有对的前缀,st1就既是str2的前缀,也是它的后缀。

直观来讲的话,p1(i) == p2(i) 意味着,str1的第i个字符和str2的第i个字符相等,1且str1的倒数第i个字符和str2的倒数第i个字符相等。如果对于任意的i这句话成立,那么显然既是前缀又是后缀。

所以这道题就从判断前后缀转换成了判断前缀。判断前缀怎么做,就变成了字典树板子。但这题主要难点在于能想到通过pair对来转换为字典树板子。

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class pair{
    int x,y;

    public pair(int x,int y){
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object p){
        if(p == null){
            return false;
        }
        if(this.getClass()!=p.getClass()){
            return false;
        }
        pair tmp = (pair) p;
        return tmp.x==x && tmp.y==y;
    }

    @Override
    public int hashCode(){
        return Objects.hash(x,y);
    }
}

class Node{
     Map<pair,Node> son = new HashMap<>();
     int cnt; // cnt仅在字符串尾部元素处计数,本质上是对该字符串出现次数的计数
}

class Solution {
    public long countPrefixSuffixPairs(String[] words) {
        long ans = 0L;
        int len;

        Node root = new Node(); // 对应空字符串
        Node cur;
        for (String word : words) {
            char[] ch = word.toCharArray();
            len = ch.length;
            cur = root;

            for (int i = 0; i < len; i++) {
                pair p = new pair(ch[i], ch[len - 1 - i]);
                // 深搜到字符串尾,没有就插入
                cur = cur.son.computeIfAbsent(p, pair -> new Node());
                // 途径的每个字符串都是前缀,加上计数
                ans += cur.cnt;
            }
            // 当前字符串计数增一
            cur.cnt++;
        }

        return ans;
    }
}

这里字典树的写法参考的灵神的,我之前板刷的一个写法太繁了。灵神在key上对pair对进行了压缩,我没压缩穿了个数对进去,还得覆写hashCode和equals,java是真的笨。

复杂度方面,hash表最快O(1),每个字符串都遍历了一遍,所以是O(∑(i=0:words.length-1)(words[i].length())。

3. LC 100268 最长公共后缀查询

周赛390T4,AK的第二把。

这题就是很板的字典树。只要稍微改下ds,对于每个节点都记录下能到达的wordsContainer索引就可以了,用小根堆记录。

import java.util.*;

class Trie{
    String[] wordsContainer;

    public Trie(String[] wordsContainer){
        this.wordsContainer = wordsContainer;
    }
    PriorityQueue<Integer> index = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            int l1 = wordsContainer[o1].length();
            int l2 = wordsContainer[o2].length();

            if(l1==l2){
                return Integer.compare(o1,o2);
            }

            return Integer.compare(l1,l2);
        }
    });
    Map<Character,Trie> child = new HashMap<>();
}

class Solution {
    public int[] stringIndices(String[] wordsContainer, String[] wordsQuery) {
        Trie root = new Trie(wordsContainer);
        for (int i = 0; i < wordsContainer.length; i++) {
            String s = wordsContainer[i];
            char[] ch = s.toCharArray();

            Trie cur = root;
            root.index.offer(i);
            for (int j = ch.length - 1; j >= 0; j--) {
                char c = ch[j];
                Trie child = cur.child.get(c);
                if(child==null){
                    cur.child.put(c,new Trie(wordsContainer));
                }
                cur = cur.child.get(c);
                cur.index.offer(i);
            }

        }

        int[] ans = new int[wordsQuery.length];
        outer:for (int i = 0; i < wordsQuery.length; i++) {
            String s = wordsQuery[i];
            char[] ch = s.toCharArray();

            Trie cur = root;
            for (int j = ch.length - 1; j >= 0; j--) {
                char c = ch[j];
                Trie tmp = cur.child.get(c);
                if(tmp==null){
                    ans[i] = cur.index.peek();
                    continue outer;
                }
                cur = tmp;
            }

            ans[i] = cur.index.peek();
        }

        return ans;
    }
}

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值