LeetCodeP208.实现Trie(前缀树)

前缀树定义

在这里插入图片描述
在这里插入图片描述

分析

先来看字典树树的大致结构

可以看到, 我们使用每条边表示一个字符, 每个结点使用一个唯一的序号标识

比如

  1. 1->2->5 表示的字符串是 aa
  2. 1->3->7 表示的字符串是 ba

通过图可以知道, 只要遵循上述约定, 假如有两个不相同的字符串有相同的前缀, 则它们可以共用一些结点, 但是这两个字符串的最后一个字符所在边指向的结点序号必定不同, 这个结论对n个不同字符串同样成立

题目要求

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false
  • 1 <= word.length, prefix.length <= 2000
  • wordprefix 仅由小写英文字母组成
  • insertsearchstartsWith 调用次数 总计 不超过 30000 次

题目分析

由于题目所要求的是建立小写英文字母的字典树, 故单个结点至多可以引出 26 条边

我们在字典树上插入字符串时, 比如插入字符串 bab, 但是字典树上只存在 ba 子串, 故需要在 6 号结点上新增一个指向 b 的边

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UL6IXUHY-1621752381574)(C:\Users\悠一木碧\AppData\Roaming\Typora\typora-user-images\image-20210523142212644.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9Ft4Ra8-1621752381576)(C:\Users\悠一木碧\AppData\Roaming\Typora\typora-user-images\image-20210523142346138.png)]

故至此, 插入 bab 字符串结束

插入

为了表示在 6 号结点建立了 b 字符的边, 我们可以使用一个二维数组 nodesPath, 其中 nodesPath[i] 表示 i 号结点

其中 26 表示单个结点最多可以有26条边, 具体问题具体分析, 因为这个问题要求的是小写英文字母

int maxNodes = 100000;
int[][] nodesPath = new int[maxNodes][26];

我们可以以 nodesPath[0] 表示字典树的根节点, 根节点不表示字符, 所有指向根节点的边, 表示没有对应字符的边

// 表示根节点没有到 a 字母结点的边
nodesPath[0][0] = 0;
// 表示根节点到 b 字母结点的边指向的是索引为 1 的结点
nodesPath[0][1] = 1

故我们可以用字段来维护结点数, 这样增加一个结点的时候, 对结点数++, 同时结点获得了一个唯一的索引序号

遍历一个字符串 word 的所有字符, 当对应的边指向根节点时, 说明要增加边了

int currentNodeIndex = 0;
for (int i = 0; i < word.length(); i++) {
     int c = word.charAt(i) - 'a';
     if (nodesPath[currentNodeIndex][c] == 0) {
         ++nodeCount;
         nodesPath[currentNodeIndex][c] = nodeCount;
     }
     currentNodeIndex = nodesPath[currentNodeIndex][c];
}

查找

当我们在字典树上新增了我们所需的全部字符后, 还需要做一个操作, 用于标记当前字符串被插入了

还记得上面我们说的: 假如有两个不相同的字符串有相同的前缀, 则它们可以共用一些结点, 但是这两个字符串的最后一个字符所在边指向的结点序号必定不同 结论吗?

思考下面一个问题

insert("abcd");
search("abc") = false ? 

插入了 abcd 后, 字典树上必定存在 abc, 但是要求返回 search(“abc”) = false, startsWith(“abc”) = true;

故可以用一个一维数组 exists

int maxNodes = 100000;
int[] exists = new int[maxNodes];

当插入一个字符串后, 设置 exists[字符串最后一个字符的唯一标识] = true

这样, 即使找到了所有字符都在字典树上, 只要 exists[index] = false, 就说明这个字符串没有插入过, 也就是 search() = false;

而 startsWith()方法只需要字符存在即可, 返回 true

实现代码

    class Trie {
        /*
         每个结点到下一个结点的路径
         */
        private final int[][] nodesPath;

        /*
        标记每个单词是否插入过字典树
         */
        private final boolean[] exist;

        /*
        结点数量
         */
        private int nodeCount;
        
        /*
        当查询不到所需字符时, 返回的索引值
         */
        private final int INDEX_WHEN_CHAR_NOT_EXISTS = -1;

        /**
         * Initialize your data structure here.
         */
        public Trie() {
            nodeCount = 0;
            nodesPath = new int[100000][26];
            exist = new boolean[100000];
        }

        public void insert(String word) {
            int currentNodeIndex = 0;
            for (int i = 0; i < word.length(); i++) {
                int targetChar = word.charAt(i) - 'a';
                if (nodesPath[currentNodeIndex][targetChar] == 0) {
                    ++nodeCount;
                    nodesPath[currentNodeIndex][targetChar] = nodeCount;
                }
                currentNodeIndex = nodesPath[currentNodeIndex][targetChar];
            }
            exist[currentNodeIndex] = true;
        }

        public boolean search(String word) {
            int index = getIndexOfLastChar(word);
            /*
            尽管每个字符都在字典树上出现过, 但是可能是其他字符串的子串, 故为了区分, 采用返回 exist数组中的值
             */
            return INDEX_WHEN_CHAR_NOT_EXISTS != index && exist[index];
        }

        /**
         * 查找对应的子串是否出现在字典树中, 只需要每个字符依次出现在字典树中即可
         */
        public boolean startsWith(String prefix) {
            int index = getIndexOfLastChar(prefix);
            return INDEX_WHEN_CHAR_NOT_EXISTS != index;
        }

        private int getIndexOfLastChar(String word) {
            int currentNodeIndex = 0;
            for (int i = 0; i < word.length(); i++) {
                int c = word.charAt(i) - 'a';
                if (nodesPath[currentNodeIndex][c] == 0) {
                    return INDEX_WHEN_CHAR_NOT_EXISTS;
                }
                currentNodeIndex = nodesPath[currentNodeIndex][c];
            }
            return currentNodeIndex;
        }
    }

运行结果

在这里插入图片描述

总结

这份代码虽能实现问题所需, 但是效率有待提高

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值