LeetCode题练习与总结:实现Trie(前缀树)--208

227 篇文章 0 订阅
137 篇文章 0 订阅

一、题目描述

Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word 。
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

示例:

输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]

解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 True
trie.search("app");     // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app");     // 返回 True

提示:

  • 1 <= word.length, prefix.length <= 2000
  • word 和 prefix 仅由小写英文字母组成
  • insertsearch 和 startsWith 调用次数 总计 不超过 3 * 10^4 次

二、解题思路

  • Trie树的基本结构:每个节点包含一个字符和一个布尔值(表示是否是单词的结尾),以及一个子节点数组(大小为26,对应26个小写英文字母)。

  • 初始化:创建一个根节点,不包含任何字符,并且不是单词的结尾。

  • 插入操作:

    • 从根节点开始,遍历字符串的每个字符。
    • 对于每个字符,判断当前节点的子节点数组中是否存在对应的字符节点,如果不存在,则创建一个新的节点。
    • 遍历到字符串的最后一个字符时,将最后一个节点标记为单词的结尾。
  • 搜索操作:

    • 从根节点开始,遍历字符串的每个字符。
    • 对于每个字符,判断当前节点的子节点数组中是否存在对应的字符节点,如果不存在,则返回false。
    • 遍历完整个字符串后,判断最后一个节点是否是单词的结尾,如果是,则返回true,否则返回false。
  • 前缀搜索操作:

    • 从根节点开始,遍历前缀的每个字符。
    • 对于每个字符,判断当前节点的子节点数组中是否存在对应的字符节点,如果不存在,则返回false。
    • 遍历完整个前缀后,返回true。

三、具体代码

class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }
    
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            if (node.children[c - 'a'] == null) {
                node.children[c - 'a'] = new TrieNode();
            }
            node = node.children[c - 'a'];
        }
        node.isEnd = true;
    }
    
    public boolean search(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            if (node.children[c - 'a'] == null) {
                return false;
            }
            node = node.children[c - 'a'];
        }
        return node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        TrieNode node = root;
        for (char c : prefix.toCharArray()) {
            if (node.children[c - 'a'] == null) {
                return false;
            }
            node = node.children[c - 'a'];
        }
        return true;
    }
}

class TrieNode {
    public TrieNode[] children;
    public boolean isEnd;

    public TrieNode() {
        children = new TrieNode[26];
        isEnd = false;
    }
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • insert 方法:

    • 对于每个插入的字符串,我们遍历一次字符串的每个字符。
    • 在每次遍历中,我们访问和可能创建一个新节点,这是一个常数时间操作(O(1))。
    • 因此,insert 方法的时间复杂度是 O(n),其中 n 是字符串的长度。
  • search 方法:

    • 对于每个搜索操作,我们遍历一次字符串的每个字符。
    • 在每次遍历中,我们检查是否存在对应的子节点,这是一个常数时间操作(O(1))。
    • 因此,search 方法的时间复杂度是 O(n),其中 n 是字符串的长度。
  • startsWith 方法:

    • 对于每个前缀搜索操作,我们遍历一次前缀的每个字符。
    • 在每次遍历中,我们检查是否存在对应的子节点,这是一个常数时间操作(O(1))。
    • 因此,startsWith 方法的时间复杂度是 O(n),其中 n 是前缀的长度。
2. 空间复杂度
  • Trie树的空间复杂度取决于插入的字符串的数量和它们的长度。

    • Trie树中的每个节点都包含一个长度为26的数组,用于存储子节点,以及一个布尔值来标记是否是单词的结尾。
    • 如果插入的字符串长度总和为 S,并且平均每个字符串的长度为 L,那么最坏情况下(没有公共前缀),Trie树的空间复杂度将是 O(S)。
    • 在实际情况下,由于字符串之间可能有公共前缀,空间复杂度会小于 O(S)。
  • 具体来说:

    • Trie树中的节点总数取决于插入的字符串中不同字符的组合数。
    • 如果所有的字符串都是完全不同的,那么空间复杂度将是 O(m * L),其中 m 是插入的字符串的数量,L 是字符串的最大长度。
    • 在最坏的情况下,即每个字符串都不共享任何前缀,空间复杂度将是 O(m * L)。
    • 在平均情况下,空间复杂度会小于 O(m * L),因为字符串之间会有共享的前缀。
3. 总结
  • insert 方法的时间复杂度:O(n),空间复杂度:O(m * L)(最坏情况),平均小于 O(m * L)。
  • search 方法的时间复杂度:O(n),空间复杂度:O(1)(不需要额外空间)。
  • startsWith 方法的时间复杂度:O(n),空间复杂度:O(1)(不需要额外空间)。

五、总结知识点

  • 数据结构

    • 树形结构:Trie树是一种树形结构,用于高效地存储和检索字符串数据集。
    • 节点TrieNode类代表Trie树中的节点,每个节点包含一个子节点数组和一个标记是否为单词结尾的布尔值。
  • 字符与整数的转换

    • 使用字符与整数的转换(char - 'a')来计算字符在子节点数组中的索引位置,从而实现只使用固定大小的数组来存储子节点。
  • 数组的初始化与访问

    • 数组的初始化:TrieNode类的构造函数中初始化了一个大小为26的TrieNode数组,用于存储26个小写英文字母的子节点。
    • 数组的访问:在insertsearchstartsWith方法中,通过字符索引来访问子节点数组。
  • 字符串遍历

    • 使用String.toCharArray()方法将字符串转换为字符数组,然后通过for-each循环遍历字符数组。
  • 递归思想

    • 虽然代码中没有显式的递归调用,但insertsearchstartsWith方法都隐含了递归的思想,即在每个节点上,根据当前字符向下移动到下一个子节点。
  • 成员变量与构造函数

    • Trie类包含一个成员变量root,它是一个TrieNode对象,代表Trie树的根节点。
    • Trie类的构造函数初始化根节点。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值