力扣labuladong——一刷day90

发布时间:2024年01月10日

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言


Trie 树又叫字典树、前缀树、单词查找树,是一种二叉树衍生出来的高级数据结构,主要应用场景是处理字符串前缀相关的操作

一、Trie树实现

public class TrieMap<V> {
    //ASCII码个数
    private static final int R = 256;
    //当前存在map中的键值对的个数
    private int size = 0;
    //Trie 树的根节点
    private TrieNode<V> root = null;
    //孩子节点(私有静态类)
    private static class TrieNode<V>{
        V val = null;
        TrieNode<V>[] children = new TrieNode[R];
    }

    /**
     * 从节点node开始搜索key如果存在则返回,不存在返回null(寻找的是节点)
     * @param node
     * @param key
     * @return
     */
    public TrieNode<V> getNode(TrieNode<V>node, String key){
        TrieNode<V> p = node;
        for (int i = 0; i < key.length(); i++) {
            char c = key.charAt(i);
            if (p == null){
                return null;
            }
            p = p.children[c];
        }
        return p;
    }

    /**
     * 获取key值对应的value
     * @param key
     * @return
     */
    public V get(String key){
        TrieNode<V> node = getNode(root,key);
        if(node != null && node.val != null){
            return node.val;
        }
        return null;
    }

    /**
     * 判断是否存在key
     * @param key
     * @return
     */
    public boolean containsKey(String key){
        return get(key) != null;
    }

    /**
     * 是否存在前缀为predix的键
     * @param prefix
     * @return
     */
    public boolean hasKeyWithPrefix(String prefix){
        return getNode(root,prefix) != null;
    }

    /**
     * 在所有键中寻找query的最短前缀
     * @param query
     * @return
     */
    public String shortestPrefixOf(String query){
        TrieNode<V> p = root;
        for (int i = 0; i < query.length(); i++) {
            char c = query.charAt(i);
            if(p == null){
                return "";
            }
            if(p.val != null){
                return query.substring(0,i);
            }
            p = p.children[c];
        }
        if (p != null && p.val != null){
            return query;
        }
        return "";
    }

    /**
     * 在所有键中寻找query最长前缀
     * @param query
     * @return
     */
    public String longestPrefixOf(String query){
        int maxLen = 0;
        TrieNode<V> p = root;
        for (int i = 0; i < query.length(); i++) {
            if(p == null){
                return "";
            }
            char c = query.charAt(i);
            if(p.val != null){
                maxLen = i;
            }
            p = p.children[c];
        }
        if (p != null && p.val != null){
            return query;
        }
        return query.substring(0,maxLen);
    }

    /**
     * 找到所以以prefix为前缀的字符串
     * @param prefix
     * @return
     */
    public List<String> keysWithPrefix(String prefix){
        List<String> res = new LinkedList<>();
        TrieNode<V> node = getNode(root,prefix);
        if (node == null){
            return res;
        }
        traverse(node, new StringBuilder(prefix),res);
        return res;
    }

    /**
     * 遍历以node节点为根的Trie树,找到所有的键
     * @param node
     * @param sb
     * @param res
     */
    private void traverse(TrieNode<V> node, StringBuilder sb, List<String> res) {
        if (node == null){
            return;
        }
        if (node.val != null){
            res.add(sb.toString());
        }
        for (char i = 0; i < R; i++) {
            sb.append(i);
            traverse(node.children[i],sb,res);
            sb.deleteCharAt(sb.length() -1);
        }
    }

    /**
     * 匹配模式串pattern得到所有的key
     * @param pattern
     * @return
     */
    public List<String> keysWithPattern(String pattern){
        List<String> res = new LinkedList<>();
        traverse(root, new StringBuilder(), pattern, 0, res);
        return res;
    }

    /**
     * 遍历函数,尝试在「以 node 为根的 Trie 树中」匹配 pattern[i..]
     * @param node
     * @param path
     * @param pattern
     * @param i
     * @param res
     */
    private void traverse(TrieNode<V> node, StringBuilder path, String pattern, int i, List<String> res) {
        if (node == null){
            return;
        }
        if (i == path.length()){
            if (node.val != null){
                res.add(path.toString());
            }
            return;
        }
        char ch = pattern.charAt(i);
        if (ch == '.'){
            for (char c = 0; c < R; c ++){
                path.append(c);
                traverse(node.children[c],path,pattern,i+1,res);
                path.deleteCharAt(path.length()-1);
            }
        }else {
            path.append(ch);
            traverse(node.children[ch],path,pattern,i +1,res);
            path.deleteCharAt(path.length()-1);
        }
    }

    /**
     * 判断是否存在模式串pattern
     * @param pattern
     * @return
     */
    public boolean hasKeyWithPattern(String pattern){
        return hasKeyWithPattern(root,pattern, 0);
    }

    /**
     * 函数定义:从 node 节点开始匹配 pattern[i..],返回是否成功匹配
     * @param node
     * @param pattern
     * @param i
     * @return
     */
    private boolean hasKeyWithPattern(TrieNode<V> node, String pattern, int i) {
        if (node == null){
            return false;
        }
        if (i == pattern.length()){
            if (node.val != null){
                return true;
            }
        }
        char ch = pattern.charAt(i);
        if (ch == '.'){
            for (char c = 0; c < R; c ++){
                if (hasKeyWithPattern(node.children[ch],pattern, i +1)){
                    return true;
                }
            }
        }else {
            return hasKeyWithPattern(node.children[ch],pattern,i+1);
        }
        return false;
    }

    /**
     * 在TrieMap中添加或者修改键值对
     * @param key
     * @param val
     */
    public void put(String key, V val){
        if (!containsKey(key)){
            size ++;
        }
        root = put(root, key, val,0);
    }

    /**
     *  定义:向以 node 为根的 Trie 树中插入 key[i..],返回插入完成后的根节点
     * @param node
     * @param key
     * @param val
     * @param i
     * @return
     */
    private TrieNode<V> put(TrieNode<V> node, String key, V val, int i) {
        if (node == null){
            node = new TrieNode<>();
        }
        if (i == key.length()){
            node.val = val;
            return node;
        }
        char c = key.charAt(i);
        node.children[c] = put(node.children[c],key,val,i+1);
        return node;
    }
    /**
     * 在 Map 中删除 key
     * @param key
     */
    public void remove(String key) {
        if (!containsKey(key)) {
            return;
        }
        // 递归修改数据结构要接收函数的返回值
        root = remove(root, key, 0);
        size--;
    }

    /**
     * 定义:在以 node 为根的 Trie 树中删除 key[i..],返回删除后的根节点
     * @param node
     * @param key
     * @param i
     * @return
     */
    private TrieNode<V> remove(TrieNode<V> node, String key, int i) {
        if (node == null) {
            return null;
        }
        if (i == key.length()) {
            // 找到了 key 对应的 TrieNode,删除 val
            node.val = null;
        } else {
            char c = key.charAt(i);
            // 递归去子树进行删除
            node.children[c] = remove(node.children[c], key, i + 1);
        }
        // 后序位置,递归路径上的节点可能需要被清理
        if (node.val != null) {
            // 如果该 TireNode 存储着 val,不需要被清理
            return node;
        }
        // 检查该 TrieNode 是否还有后缀
        for (int c = 0; c < R; c++) {
            if (node.children[c] != null) {
                // 只要存在一个子节点(后缀树枝),就不需要被清理
                return node;
            }
        }
        // 既没有存储 val,也没有后缀树枝,则该节点需要被清理
        return null;
    }
}

文章来源:https://blog.csdn.net/ResNet156/article/details/135513557
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。