LeetCode刷题 --字典树

Trie是一种树型的数据结构,用于检索字符串数据集中的键。Trie树是一个有根的树,主要思想是用字符串的共用前缀来节约存储空间。

如图所示:

LeetCode刷题 --字典树_第1张图片

从根查找,黄色的标识是一个单词的最尾端,上图的树中存了以下单词:a, abc, abd, bc, bd;

 208. 实现 Trie (前缀树)

此道例题就是Trie树的实现方式

public class Trie {
    // 字典树中,定义两个属性,一个是 Trie数组
    private Trie[] children;
    // 一个是结束标记,代表所存储的单词的终点
    private boolean isEnd;

    // 构造器
    public Trie() {
        // 赋初始值,以小写英文为例,26个字母,因此定义长度为26
        children = new Trie[26];
        // 默认结束标记为false
        isEnd = false;
    }

    // 插入数据
    public void insert(String word) {
        // 插入数据时,根节点默认就是自己
        Trie node = this;
        // 对插入的单词进行循环遍历
        for (int i = 0; i < word.length(); i++) {
            // 逐个取出要遍历的字符
            char c = word.charAt(i);
            // 与a之间的差值,就代表了在children数组中的第几位
            int index = c - 'a';
            // 判断,如果当前坐标节点为null,那么就新创建一个节点赋值进去
            if (node.children[index] == null) {
                node.children[index] = new Trie();
            }
            // 把新创建的节点赋值给当前node
            // 因为是树型结构
            node = node.children[index];
        }
        // 单词遍历结束了,就将当前node的结束标记设置为true
        node.isEnd = true;
    }

    // 查询单词是否在字典树中
    public boolean search(String word) {
        // 查找是否是前缀
        Trie node = searchPrefix(word);
        // 如果前缀查找到的节点不是null,并且,这个节点的结束标记为false,说明存在于字典树中
        // (字典树中存了abc,但是没有存ab, 查询ab时,node不为null,但是node.isEnd是false)
        return node != null && node.isEnd;

    }

    // 查询prefix是否是之前插入字符串的前缀
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    // 查找是否是前缀
    private Trie searchPrefix(String prefix) {
        // 还是从根节点开始寻找
        Trie node = this;
        // 遍历字符串
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix.charAt(i);
            int index = c - 'a';
            // 如果当前坐标节点为null,说明prefix这个单词在之前没有存入到这个树中,因此返回null
            if (node.children[index] == null) {
                return null;
            }
            // 否则就把当前坐标下的节点赋值给node,然后进行下一次的遍历
            node = node.children[index];
        }
        // 如果遍历完了,并且都有数据,那就返回当前的节点
        return node;
    }

    public static void main(String[] args) {
        Trie trie = new Trie();
        trie.insert("abc");
        trie.insert("abcd");
    }

820. 单词的压缩编码

题目:

给个数组words = ["time", "me", "bell"] ,压缩后为:time#bell# ,求压缩后的长度

解析:

前面了解了字典树的概念,字典树是通过利用字符串的公共前缀来节约存储空间,这道题目也是节约存储空间,但是用的是后缀。

分析前面给出的Trie树的实现代码,其实不难发现,只要插入字符串的时候,把从前往后遍历改成从后往前遍历,就变成了后缀。

同时要注意一下本题隐藏的一个小问题:给的数组为["me","time"],如果不排序直接处理,插入后得到的是 me#time#,但是实际上应该是time#,因此需要先对数组按照单词长度进行排序。

代码

public class Test820 {
    public int minimumLengthEncoding(String[] words) {
        Trie node = new Trie();
        int sum = 0;

        // 这个位置需要先对数组按照长度排序,如["me","time"]
        // 如果不先排序,插入的是 me#time#,但是实际上应该是time#
        Arrays.sort(words, (o1, o2) -> o2.length() - o1.length());
        // 循环遍历数组
        for (String word : words) {
            // 如果已经包含了,就跳过
            if (node.startsWith(word)) {
                continue;
            }
            // 没有包含就执行插入操作
            node.insert(word);
            // 计数器加上当前插入单词的长度,在加一个“#”的长度1
            sum += word.length() + 1;
        }
        return sum;
    }

    class Trie {
        // 定义字典树的两个属性,children 和 isEnd标记。
        Trie[] children;

        boolean isEnd;

        // 构造器
        public Trie() {
            children = new Trie[26];
            isEnd = false;
        }

        // 插入操作,使用trie相关的题目共同点就是需要大量的判断有没有某个前缀/后缀
        // 前缀就是单词从前往后遍历,后缀就是单词从后往前遍历
        // 本题要求的是找后缀,因此插入遍历时,要从后往前遍历
        public void insert(String word) {
            // Trie树的插入操作,模板式代码
            Trie node = this;
            for (int i = word.length() - 1; i >= 0; i--) {
                char c = word.charAt(i);
                int index = c - 'a';
                if (node.children[index] == null) {
                    node.children[index] = new Trie();
                }
                node = node.children[index];
            }
            node.isEnd = true;
        }

        // 判断一个单词是否是树中某个单词的前缀/后缀,或者本身
        public boolean startsWith(String prefix) {
            Trie node = isPrefix(prefix);
            return node != null;
        }

        // 判断一个单词是否是树中某个单词的前缀/后缀,或者本身
        // 模板式代码,返回一个node对象
        private Trie isPrefix(String prefix) {
            Trie node = this;
            for (int i = prefix.length() - 1; i >= 0; i--) {
                char c = prefix.charAt(i);
                int index = c - 'a';
                if (node.children[index] == null) {
                    return null;
                }
                node = node.children[index];
            }
            return node;
        }
    }

    // 另外一种效率较低的实现方式
    public int minimumLengthEncoding1(String[] words) {
        Arrays.sort(words, (o1, o2) -> o2.length() - o1.length());
        StringBuilder sb = new StringBuilder();
        for (String word : words) {
            if (sb.toString().contains(word + "#")) {
                continue;
            }
            sb.append(word).append("#");
        }
        return sb.length();
    }
}

你可能感兴趣的:(leetcode,java,算法)