【Hot 100】208. 实现 Trie (前缀树)

news2025/5/15 3:04:10

目录

  • 引言
  • 实现 Trie (前缀树)
    • 我的解题
    • 代码解析
      • 代码思路分析
      • 优化建议
        • 1. 内存泄漏问题
        • 2. 使用智能指针优化内存管理
        • 3. 输入合法性校验(可选)
        • 4. 其他优化
      • 总结

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:算法专栏
  • 💥 标题:【Hot 100】208. 实现 Trie (前缀树)
  • ❣️ 寄语:书到用时方恨少,事非经过不知难!

引言

实现 Trie (前缀树)

  • 🎈 题目链接:
  • 🎈 做题状态:

我的解题

首先需要理解前缀树的定义,前缀树是一颗多叉树,树根不存储字母。每一层可能存储26个不同的字母。然后每一个单词对应这个多叉树的一条路径,并且路径的结尾会标识是单词的结尾。

class Trie {
private:
    bool isEnd;
    Trie* next[26]; //指针数组,有26个小写字母

public:
    Trie() {
        isEnd = false;
        memset(next, 0, sizeof(next));
    }
    
    // 插入一个单词
    void insert(string word) {

        // node指向根节点并向下遍历
        Trie* node = this;
        for (char c : word)
        {
            // 判断当前这个字母是否在当前层存在,如果不存在则创建一个新的树。
            if (node->next[c-'a'] == nullptr)
            {
                node->next[c-'a'] = new Trie();
            }
            node = node->next[c-'a'];   // 继续往下遍历
        }

        // 遍历到末尾后,需要标识 end
        node->isEnd = true;
    }
    
    // 搜索当前单词是否存在,依次比较每个单词是否存在每一层中
    bool search(string word) {
        Trie* node = this;
        for (const char& c : word)
        {
            if (node->next[c-'a'] == nullptr)
            {
                return false;
            }
            node = node->next[c-'a'];
        }

        return node->isEnd; // 遍历到末尾后还需要判断是否是单词的结尾
    }
    
    // 判断这个前缀树是否包含 prefix 这个前缀
    bool startsWith(string prefix) {
        Trie* node = this;
        for (const char& c : prefix)
        {
            if (node->next[c-'a'] == nullptr)
            {
                return false;
            }
            node = node->next[c-'a'];
        }

        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

代码解析

该代码实现了一个基本的前缀树(Trie)结构,支持插入、搜索和前缀匹配功能。以下是对代码的详细分析和优化建议:


代码思路分析

  1. 数据结构设计

    • isEnd:标记当前节点是否为单词的结尾。
    • next[26]:一个包含 26 个指针的数组,对应英文小写字母,用于构建字符到子节点的映射。
  2. 核心方法

    • 插入 (insert): 从根节点开始,逐字符向下遍历。若字符对应的子节点不存在,则创建新节点。遍历完成后,标记最后一个节点为单词结尾。
    • 搜索 (search): 检查单词是否存在且最后一个节点被标记为结尾。
    • 前缀匹配 (startsWith): 仅检查路径是否存在,无需判断结尾标记。
  3. 正确性

    • 逻辑正确,符合前缀树的标准实现。
    • 使用 memset 初始化指针数组,确保初始值为 nullptr

优化建议

1. 内存泄漏问题

当前代码未释放动态分配的子节点,导致内存泄漏。需添加析构函数递归释放子节点:

~Trie() {
    for (int i = 0; i < 26; ++i) {
        delete next[i]; // 递归调用子节点的析构函数
    }
}
2. 使用智能指针优化内存管理

改用 std::unique_ptr 管理子节点,避免手动释放内存:

class Trie {
private:
    bool isEnd;
    std::array<std::unique_ptr<Trie>, 26> next; // 使用智能指针

public:
    Trie() : isEnd(false) {} // 无需 memset

    void insert(string word) {
        Trie* node = this;
        for (char c : word) {
            if (!node->next[c - 'a']) {
                node->next[c - 'a'] = std::make_unique<Trie>();
            }
            node = node->next[c - 'a'].get();
        }
        node->isEnd = true;
    }

    // search 和 startsWith 无需修改
};
3. 输入合法性校验(可选)

若需处理非法字符(如大写字母),可在插入/查询前添加校验:

void insert(string word) {
    for (char c : word) {
        if (c < 'a' || c > 'z') {
            throw std::invalid_argument("Invalid character");
        }
    }
    // 原插入逻辑
}
4. 其他优化
  • 空间优化:使用压缩 Trie 树(如 Ternary Search Tree)减少内存占用,但会增加实现复杂度。
  • 性能优化:当前时间复杂度为 O(L)(L 为字符串长度),已是最优,无需调整。

总结

代码逻辑正确,但存在内存泄漏问题。建议通过析构函数或智能指针优化内存管理。其他优化可根据实际需求选择。改进后的代码示例(使用智能指针)如下:

#include <memory>   // 用于智能指针 unique_ptr
#include <array>    // 用于固定大小的数组 array
#include <string>   // 用于字符串操作

class Trie {
private:
    // 标记当前节点是否为某个单词的结尾
    bool isEnd;
    
    // 使用智能指针管理子节点,避免内存泄漏
    // 数组大小为26,对应英文小写字母a-z
    std::array<std::unique_ptr<Trie>, 26> next;

public:
    // 构造函数:初始化 isEnd 为 false,表示初始时不是单词结尾
    // 智能指针数组 next 会自动初始化为 nullptr
    Trie() : isEnd(false) { }
    
    /**
     * 插入一个单词到 Trie 树中
     * @param word 待插入的单词
     */
    void insert(const std::string& word) {
        // 从根节点(this)开始遍历
        Trie* node = this;
        
        // 逐个字符处理
        for (char c : word) {
            // 计算字符对应的索引(a->0, b->1, ..., z->25)
            int idx = c - 'a';
            
            // 如果当前字符的子节点不存在,则创建新节点
            if (node->next[idx] == nullptr) {
                node->next[idx] = std::make_unique<Trie>();
            }
            
            // 移动到子节点继续处理
            node = node->next[idx].get();  // get() 获取裸指针
        }
        
        // 标记单词的最后一个字符节点为结尾
        node->isEnd = true;
    }
    
    /**
     * 搜索 Trie 树中是否存在某个单词
     * @param word 待搜索的单词
     * @return 如果单词存在且完整匹配(最后一个字符是结尾),返回 true;否则返回 false
     */
    bool search(const std::string& word) {
        // 从根节点开始遍历
        Trie* node = this;
        
        // 逐个字符检查
        for (const char& c : word) {
            int idx = c - 'a';
            
            // 如果当前字符的子节点不存在,说明单词不存在
            if (node->next[idx] == nullptr) {
                return false;
            }
            
            // 移动到子节点继续检查
            node = node->next[idx].get();
        }
        
        // 检查最后一个字符是否被标记为单词结尾
        return node->isEnd;
    }
    
    /**
     * 检查 Trie 树中是否存在某个前缀
     * @param prefix 待检查的前缀
     * @return 如果前缀存在(不要求是完整单词),返回 true;否则返回 false
     */
    bool startsWith(const std::string& prefix) {
        // 从根节点开始遍历
        Trie* node = this;
        
        // 逐个字符检查
        for (const char& c : prefix) {
            int idx = c - 'a';
            
            // 如果当前字符的子节点不存在,说明前缀不存在
            if (node->next[idx] == nullptr) {
                return false;
            }
            
            // 移动到子节点继续检查
            node = node->next[idx].get();
        }
        
        // 只要路径存在,无论是否是单词结尾,都返回 true
        return true;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375763.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2025最新】Vm虚拟机中直接使用Ubuntu 免安装过程直接使用教程与下载

Ubuntu 是一个基于 Debian 的自由开源 Linux 操作系统&#xff0c;面向桌面、服务器和云计算平台广泛应用。 由英国公司 Canonical Ltd. 维护和发布&#xff0c;Ubuntu 强调易用性、安全性和稳定性&#xff0c;适合个人用户、开发者以及企业部署使用。 Ubuntu 默认使用 GNOME …

Vue2 elementUI 二次封装命令式表单弹框组件

需求&#xff1a;封装一个表单弹框组件&#xff0c;弹框和表单是两个组件&#xff0c;表单组件以插槽的形式动态传入弹框组件中。 外部组件使用的方式如下&#xff1a; 直接上代码&#xff1a; MyDialog.vue 弹框组件 <template><el-dialog:titletitle:visible.syn…

Antd中Form详解:

1.获取Form表单值的方式: ① 使用Form.useForm()钩子&#xff08;推荐方式&#xff09; const [form] Form.useForm();const getFormValues () > {const values form.getFieldsValue();};<Form form{form}>...<Form.Item label{null}><Button onClick{ge…

docker系列-DockerDesktop报错信息(Windows Hypervisor is not present)

Docker Desktop 报错信息 Docker Desktop - Windows Hypervisor is not present Docker Desktop is unable to detect a Hypervisor. Hardware assisted virtualization and data execution protection must be enabled in the BIOS.这是因为 Docker Desktop 需要启用 虚拟化技…

《基于 Kubernetes 的 WordPress 高可用部署实践:从 MariaDB 到 Nginx 反向代理》

手把手教你用 Kubernetes 部署高可用 WordPress 博客 本实验通过 Kubernetes 容器编排平台&#xff0c;完整部署了一个高可用的 WordPress 网站架构&#xff0c;包含 MariaDB 数据库、WordPress 应用和 Nginx 反向代理三大核心组件。实验涵盖了从基础环境准备到最终服务暴露的…

Ubuntu源码版comfyui的安装

Comfyui也出桌面版了&#xff0c;但是想让大家多个人都使用怎么办呢&#xff1f;也有方法&#xff0c;安装Linux版&#xff0c;启动后会生成个网页地址&#xff0c;打开就能用了。 1、先来看下本地安装环境配置&#xff1a; 系统&#xff1a;Ubuntu 22.04 内存&#xff1a;2…

制作一款打飞机游戏47:跳转

编辑器的问题 我们开始为不同的敌人编写一些行为&#xff0c;到目前为止进展顺利&#xff0c;一切都很棒。但上次我们遇到了一些问题&#xff0c;我们发现在这个编辑器中编写代码有时有点困难&#xff0c;因为当你想要在某行之间插入内容时&#xff0c;你不得不删除一切然后重…

本地部署ollama及deepseek(linux版)

一、安装ollama export OLLAMA_MIRROR"https://ghproxy.cn/https://github.com/ollama/ollama/releases/latest/download"curl -fsSL https://ollama.com/install.sh | sed "s|https://ollama.com/download|$OLLAMA_MIRROR|g" | shexport OLLAMA_MIRROR&q…

vue H5解决安卓手机软键盘弹出,页面高度被顶起

开发中安卓机上遇到的软键盘弹出导致布局问题 直接上代码_ 在这里插入代码片 <div class"container"><div class"appContainer" :style"{height:isKeyboardOpen? Heights :inherit}"><p class"name"><!-- 绑定…

CSS专题之自定义属性

前言 石匠敲击石头的第 12 次 CSS 自定义属性是现代 CSS 的一个强大特性&#xff0c;可以说是前端开发需知、必会的知识点&#xff0c;本篇文章就来好好梳理一下&#xff0c;如果哪里写的有问题欢迎指出。 什么是 CSS 自定义属性 CSS 自定义属性英文全称是 CSS Custom Proper…

七、深入 Hive DDL:管理表、分区与洞察元数据

作者&#xff1a;IvanCodes 日期&#xff1a;2025年5月13日 专栏&#xff1a;Hive教程 内容导航 一、表的 DDL 操作 (非创建)二、分区的 DDL 操作三、洞察元数据&#xff1a;SHOW 命令的威力结语&#xff1a;DDL 与 SHOW&#xff0c;Hive 管理的双翼练习题一、选择题二、代码题…

直接在Excel中用Python Matplotlib/Seaborn/Plotly......

本次分享如何利用pyxll包&#xff0c;实现直接在Excel中使用Python Matplotlib/Seaborn/Plotly等强大可视化工具。 pyxll配置 pyxll安装 pip install pyxll pyxll install pyxll自定义方法 例如&#xff0c;自定义一个计算斐波那契数的方法fib&#xff0c;并使用pyxll装饰器…

React面试常问问题详解

以下是30个React面试中常见的问题及简要解析&#xff0c;涵盖基础概念、核心原理、性能优化、Hooks、状态管理等方面&#xff0c;适用于初中高级开发者准备面试时参考&#xff1a; 一、React 基础与核心概念 React 是什么&#xff1f; React 是由 Facebook 开发的用于构建用户界…

【Java】网络编程(Socket)

网络编程 Socket 我们开发的网络应用程序位于应用层&#xff0c;TCP和UDP属于传输层协议&#xff0c;在应用层如何使用传输层的服务呢&#xff1f;在应用层和传输层之间&#xff0c;则使用套接字Socket来进行分离 套接字就像是传输层为应用层开的一个小口&#xff0c;应用程…

思科(Cisco ASA/Firepower)、华三(H3C)、华为(Huawei USG)防火墙 的基础配置

以下是针对 思科&#xff08;Cisco ASA/Firepower&#xff09;、华三&#xff08;H3C&#xff09;、华为&#xff08;Huawei USG&#xff09;防火墙 的基础配置指南&#xff0c;涵盖 区域划分、安全策略、NAT、路由 等核心功能。配置示例基于通用场景&#xff0c;实际部署时需根…

Windows环境下maven的安装与配置

1.检查JAVA_HOME环境变量 Maven是使用java开发的&#xff0c;所以必须知道当前系统环境中的JDK的安装目录。 搜索栏直接输入“cmd” 或者 WinR 输入cmd 在打开的终端窗口输入“echo %JAVA_HOME”&#xff0c;就可以看到jdk的位置了。 如果没有的话&#xff0c;请参考我的文章&a…

LeetCode:513、找树左下角的值

//递归法 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* t…

Vxe UI vue vxe-table 实现表格数据分组功能,不是使用树结构,直接数据分组

Vxe UI vue vxe-table 实现表格数据分组功能&#xff0c;不是使用树结构&#xff0c;直接数据分组 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.com/x-extends/vxe-table gitee&#xff1a;https://gitee.com/x-extends/vxe-table 代码 通过…

如何禁止chrome自动更新

百度了一下 下面这个方法实测有效 目录 1、WINR 输入 services.msc 2、在Services弹窗中找到下面两个service并disable 3、验证是否禁止更新成功&#xff1a; 1、WINR 输入 services.msc 2、在Services弹窗中找到下面两个service并disable GoogleUpdater InternalService…

阳光学院【2020下】计算机网络原理-A卷-试卷-期末考试试卷

一、单选题&#xff08;共25分&#xff0c;每空1分&#xff09; 1.ICMP协议工作在TCP/IP参考模型的 ( ) A.主机-网络 B.网络互联层 C.传输层 D.应用层 2.下列关于交换技术的说法中&#xff0c;错误的是 ( ) A.电路交换适用于突发式通信 B.报文交换不能满足实时通信 C.报文…