前缀树进阶-经典案例详解

前缀树进阶-经典案例详解

    • 一、前缀树基础内容回顾
    • 二、单词搜索建议系统
      • 2.1 问题描述
      • 2.2 解题思路
      • 2.3 Java代码实现
      • 2.4 复杂度分析
    • 三、单词编码
      • 3.1 问题描述
      • 3.2 解题思路
      • 3.3 Java代码实现
      • 3.4 复杂度分析
    • 四、最长单词
      • 4.1 问题描述
      • 4.2 解题思路
      • 4.3 Java代码实现
      • 4.4 复杂度分析

我在上篇博客介绍了前缀树(Trie树)这种高效的数据结构,以独特的树形结构和前缀匹配特性,在字符串处理、搜索建议、词频统计等众多场景中发挥着关键作用。本文我就将通过几个经典案例,深入剖析前缀树在实际问题中的应用,结合Java代码实现,帮你掌握前缀树的使用技巧和解题思路。

一、前缀树基础内容回顾

前缀树是一种多叉树结构,其每个节点代表一个字符,从根节点到某一节点的路径上的字符连接起来,形成的字符串即为该节点对应的字符串。前缀树的核心优势在于高效的前缀匹配字符串查找,能够在 O ( m ) O(m) O(m)的时间复杂度内完成长度为 m m m的字符串的插入、查询操作,其中 m m m为字符串的长度。在Java中,前缀树节点可以用如下类表示:

class TrieNode {TrieNode[] children;boolean isEndOfWord;TrieNode() {children = new TrieNode[26];isEndOfWord = false;}
}

前缀树的基本操作包括插入、查询和删除:

class Trie {private TrieNode root;public Trie() {root = new TrieNode();}// 插入字符串public void insert(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {node.children[index] = new TrieNode();}node = node.children[index];}node.isEndOfWord = true;}// 查询字符串是否存在public boolean search(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {return false;}node = node.children[index];}return node.isEndOfWord;}// 查询是否存在以prefix为前缀的字符串public boolean startsWith(String prefix) {TrieNode node = root;for (char c : prefix.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {return false;}node = node.children[index];}return true;}
}

二、单词搜索建议系统

2.1 问题描述

给你一个产品列表 products 和一个搜索词 searchWord 。请你设计一个推荐系统,在依次输入单词 searchWord 的每一个字母后,推荐 products 数组中前缀与 searchWord 相同的最多三个产品。如果前缀相同的可推荐产品超过三个,请按字典序返回最小的三个。

2.2 解题思路

  1. 构建前缀树:将 products 数组中的每个单词插入前缀树中,在插入过程中,记录每个节点对应的所有单词(可使用列表存储),并按字典序排序。
  2. 遍历搜索词:依次遍历 searchWord 的每个字符,在前缀树中查找对应的节点。
  3. 获取推荐列表:若找到对应节点,则从该节点记录的单词列表中取出字典序最小的最多三个单词作为推荐;若未找到对应节点,则返回空列表。

2.3 Java代码实现

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;class TrieNode {TrieNode[] children;List<String> words;TrieNode() {children = new TrieNode[26];words = new ArrayList<>();}
}class Trie {private TrieNode root;public Trie() {root = new TrieNode();}public void insert(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {node.children[index] = new TrieNode();}node = node.children[index];node.words.add(word);}}public List<List<String>> search(String searchWord) {List<List<String>> result = new ArrayList<>();TrieNode node = root;for (char c : searchWord.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {while (result.size() < searchWord.length()) {result.add(new ArrayList<>());}return result;}node = node.children[index];Collections.sort(node.words);int count = Math.min(3, node.words.size());List<String> subList = node.words.subList(0, count);result.add(subList);}return result;}
}public class SearchSuggestionsSystem {public static List<List<String>> suggestedProducts(String[] products, String searchWord) {Trie trie = new Trie();for (String product : products) {trie.insert(product);}return trie.search(searchWord);}public static void main(String[] args) {String[] products = {"mobile", "mouse", "moneypot", "monitor", "mousepad"};String searchWord = "mouse";List<List<String>> result = suggestedProducts(products, searchWord);for (List<String> subList : result) {System.out.println(subList);}}
}

2.4 复杂度分析

  • 时间复杂度:构建前缀树的时间复杂度为 O ( n × m ) O(n \times m) O(n×m),其中 n n nproducts 数组的长度, m m m 是字符串的平均长度;每次搜索的时间复杂度为 O ( m ) O(m) O(m),遍历 searchWord 的每个字符进行查找,所以总的时间复杂度为 O ( n × m + m × k ) O(n \times m + m \times k) O(n×m+m×k) k k ksearchWord 的长度。
  • 空间复杂度:前缀树占用的空间与 products 数组中的单词数量和长度有关,最坏情况下空间复杂度为 O ( n × m ) O(n \times m) O(n×m);存储结果列表也需要一定空间,总体空间复杂度为 O ( n × m ) O(n \times m) O(n×m)

三、单词编码

3.1 问题描述

请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。

  • addWord(word):添加一个单词到数据结构中。
  • search(word):如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回 falseword 中可能包含一些 '.' ,每个 '.' 都可以表示任何一个字母。

3.2 解题思路

  1. 构建前缀树:与普通前缀树构建方式相同,将单词插入前缀树中。
  2. 搜索匹配:在搜索时,若遇到字符 '.' ,则递归遍历当前节点的所有子节点;若遇到普通字符,则按正常前缀树查找方式进行匹配。当遍历到单词末尾且对应节点标记为单词结束时,返回 true ,否则返回 false

3.3 Java代码实现

class TrieNode {TrieNode[] children;boolean isEndOfWord;TrieNode() {children = new TrieNode[26];isEndOfWord = false;}
}class WordDictionary {private TrieNode root;public WordDictionary() {root = new TrieNode();}public void addWord(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {node.children[index] = new TrieNode();}node = node.children[index];}node.isEndOfWord = true;}public boolean search(String word) {return searchHelper(word, 0, root);}private boolean searchHelper(String word, int index, TrieNode node) {if (index == word.length()) {return node.isEndOfWord;}char c = word.charAt(index);if (c != '.') {int childIndex = c - 'a';if (node.children[childIndex] == null) {return false;}return searchHelper(word, index + 1, node.children[childIndex]);} else {for (TrieNode child : node.children) {if (child != null && searchHelper(word, index + 1, child)) {return true;}}return false;}}
}public class WordDictionaryExample {public static void main(String[] args) {WordDictionary wordDictionary = new WordDictionary();wordDictionary.addWord("bad");wordDictionary.addWord("dad");wordDictionary.addWord("mad");System.out.println(wordDictionary.search("pad"));  // 输出 falseSystem.out.println(wordDictionary.search("bad"));  // 输出 trueSystem.out.println(wordDictionary.search(".ad"));  // 输出 trueSystem.out.println(wordDictionary.search("b.."));  // 输出 true}
}

3.4 复杂度分析

  • 时间复杂度:插入操作的时间复杂度为 O ( m ) O(m) O(m) m m m 为单词长度;搜索操作在最坏情况下,对于包含 '.' 的单词,需要遍历当前节点的所有子节点,时间复杂度为 O ( 26 m ) O(26^m) O(26m),但平均情况下,若 '.' 出现较少,时间复杂度接近 O ( m ) O(m) O(m)
  • 空间复杂度:前缀树占用空间为 O ( n × m ) O(n \times m) O(n×m) n n n 为单词数量, m m m 为单词平均长度 。

四、最长单词

4.1 问题描述

给出一个字符串数组 words ,找出数组中最长的单词,该单词是由 words 词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。

4.2 解题思路

  1. 构建前缀树:将 words 数组中的每个单词插入前缀树中,同时记录每个单词的长度。
  2. 遍历单词:遍历 words 数组,对于每个单词,检查其前缀是否都在前缀树中且前缀对应的节点标记为单词结束,若满足条件且单词长度更长或长度相同但字典序更小,则更新结果。

4.3 Java代码实现

class TrieNode {TrieNode[] children;boolean isEndOfWord;int length;TrieNode() {children = new TrieNode[26];isEndOfWord = false;length = 0;}
}class Trie {private TrieNode root;public Trie() {root = new TrieNode();}public void insert(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null) {node.children[index] = new TrieNode();}node = node.children[index];}node.isEndOfWord = true;node.length = word.length();}public boolean checkPrefixes(String word) {TrieNode node = root;for (char c : word.toCharArray()) {int index = c - 'a';if (node.children[index] == null ||!node.children[index].isEndOfWord) {return false;}node = node.children[index];}return true;}
}public class LongestWordInDictionary {public static String longestWord(String[] words) {Trie trie = new Trie();for (String word : words) {trie.insert(word);}String result = "";for (String word : words) {if (trie.checkPrefixes(word)) {if (word.length() > result.length() || (word.length() == result.length() && word.compareTo(result) < 0)) {result = word;}}}return result;}public static void main(String[] args) {String[] words = {"w", "wo", "wor", "worl", "world"};System.out.println(longestWord(words));  // 输出 world}
}

4.4 复杂度分析

  • 时间复杂度:构建前缀树的时间复杂度为 O ( n × m ) O(n \times m) O(n×m) n n n 为单词数量, m m m 为单词平均长度;检查每个单词前缀的时间复杂度为 O ( m ) O(m) O(m),遍历所有单词检查前缀,总的时间复杂度为 O ( n × m ) O(n \times m) O(n×m)
  • 空间复杂度:前缀树占用空间为 O ( n × m ) O(n \times m) O(n×m)

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

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

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

相关文章

Redis集群实现方式

✅ 一、什么是 Redis 集群&#xff08;Redis Cluster&#xff09; Redis 集群是 Redis 官方在 3.0 版本引入的分布式部署方案&#xff0c;它的目标是解决以下几个问题&#xff1a; 单个 Redis 实例容量有限&#xff08;最多只能使用一个服务器的内存&#xff09; 单点故障&am…

《中国电信运营商骨干网:历史、现状与未来演进》系列 第五篇:新玩家入局——中国广电CBNNET如何构建全国一张网?

专栏引言 在中国电信、联通、移动三足鼎立的骨干网格局中&#xff0c;一位身负特殊使命的“国家队新兵”正加速入场。它就是中国广电。根据2023年发布的《广电网络融合发展战略》&#xff0c;其核心任务是构建一张“新型广电网络”。手握700MHz“黄金频段”和5G牌照&#xff0c…

QT 国际化 翻译 总结

目录 生成TS文件 单纯Qt Creator工程 生成ts文件方式一&#xff1a;creator方式 生成ts文件方式二&#xff1a;命令行方式 vs2019QT工程 CMake工程 生成qm文件 代码 需要先根据ui产生ts文件&#xff0c;再根据ts文件产生qm文件&#xff0c;然后代码加载 生成TS文件 单…

Java 中实现 Excel 导入一些疑难杂症

在 Java 中实现 Excel 导入功能时&#xff0c;除了已讨论的字段映射、类型转换和内存管理外&#xff0c;还需注意以下关键问题&#xff0c;结合常见踩坑点和最佳实践总结如下&#xff1a; ⚙️ 一、文件与格式校验 文件类型与版本兼容性 明确区分 .xls&#xff08;HSSF&#x…

修改Docker-compose使Uptime-Kuma支持IPV6

之前部署了一个Uptime-Kuma用来监控服务的运行&#xff0c;最近&#xff0c;在监控IPV6网络的时候出现了一点问题&#xff0c;Docker不支持IPV6网络&#xff1a; 解决方案&#xff1a; 修改/etc/docker/daemon.json文件 {"experimental": true,"fixed-cidr-v6&…

分布式存储架构的优势

分布式存储架构通过将数据分散存储在多个物理节点上&#xff0c;在性能、可靠性及成本效益方面展现显著优势&#xff0c;具体核心优势如下&#xff1a; 一、‌弹性扩展能力‌ 水平无缝扩容‌ 通过添加节点即可线性扩展存储容量与性能&#xff0c;支持EB级数据规模&#xff0…

【4目全景】基于海思3403平台开发4目360°全景拼接相机方案

此文主要介绍基于海思3403平台通过实时视频采集&拼接&融合&显示实现实时全景空间漫游体验&#xff0c;该模组将4路视频拼接成一幅360全景图&#xff0c;涉及到计算机视觉、计算机图形学、数字视频处理等技术。 基本开发步骤主要包括以下几个方面&#xff1a;4路视频…

element-plus 按钮 展开/隐藏

文章目录 1、小记2、页面3、typescript事件4、测试数据5、样式 1、小记 element-plus中el-table 的 expand,箭头控制子项显示&#xff0c;有点丑。 想实现类似bootstrap &#xff0c;用按钮 展开/隐藏子项的功能 2、页面 <!-- 表内容 --><el-table:data"tabl…

SSE(Server-Sent Events)、WebSocket和Polling的对比

1. 基本概念 协议通信模式协议层数据流向连接方式SSE服务器单向推送基于HTTP/HTTPS服务器→客户端&#xff08;单向&#xff09;持久化TCP连接WebSocket全双工通信独立协议&#xff08;基于TCP&#xff09;服务器↔客户端&#xff08;双向&#xff09;持久化TCP连接&#xff0…

不同类型的微型导轨精度降低速度有何差异?

微型导轨是一种高精度、小体积、轻量化的直线运动导轨系统&#xff0c;广泛应用于各种需要精密直线运动的领域。其精度等级是衡量其性能的重要指标&#xff0c;不同精度等级的导轨适用于不同的应用场景。那么&#xff0c;不同类型的微型导轨精度降低速度有何差异&#xff1f; 滚…

debian挂载新硬盘后不识别怎么办?

在实际服务器部署或本地系统扩容的过程中&#xff0c;为 Debian 系统添加新硬盘是常见操作。无论是物理服务器、云服务器还是虚拟机环境中&#xff0c;当添加一块新硬盘之后&#xff0c;我们的期望很简单——系统应立即识别并支持挂载使用。 但理想归理想&#xff0c;现实却常…

nt!MiFlushSectionInternal函数分析从nt!IoSynchronousPageWrite函数到Ntfs!NtfsFsdWrite函数

第一部分&#xff1a; while (TRUE) { KeClearEvent (&IoEvent); Status IoSynchronousPageWrite (FilePointer, Mdl, (PLARGE_INTEGER)&StartingOffset…

开发Qt程序时,为什么是CMake?

开发Qt程序时&#xff0c;为什么是CMake&#xff1f; 什么是CMake&#xff1f; CMake 是一个跨平台的构建工具&#xff0c;用来管理 C/C 项目的编译过程。它通过读取 CMakeLists.txt 配置文件&#xff0c;自动生成适合不同操作系统和编译器的构建脚本&#xff08;比如 Makefi…

web布局10

Grid 布局指的是 CSS Grid Layout &#xff0c;它和以往 CSS 框架&#xff08;CSS Framework&#xff09;中所说的网格系统&#xff08;Grid System&#xff09;有所不同。至今为止&#xff0c;它是唯一一个具有二维能力的布局系统&#xff0c;即&#xff0c;它是一个基于二维网…

Spring AI 项目实战(十二):Spring Boot +AI + DeepSeek + 百度OCR 公司发票智能处理系统的技术实践(附完整源码)

系列文章 序号文章名称1Spring AI 项目实战(一):Spring AI 核心模块入门2Spring AI 项目实战(二):Spring Boot + AI + DeepSeek 深度实战(附完整源码)3Spring AI 项目实战(三):Spring Boot + AI + DeepSeek 打造智能客服系统(附完整源码)4

【FR801xH】Ubuntu24.04搭建富芮坤FR801xH系列开发环境教程

00. 目录 文章目录 00. 目录01. FR801xH概述02. FR801xH特性03. gcc-arm-none-eabi-10.3-2021.10概述04. gcc-arm-none-eabi-10.3-2021.10下载05. gcc-arm-none-eabi-10.3-2021.10安装06. FR801xH-SDK编译07. 附录 01. FR801xH概述 FR801xH 系列芯片是面向 SOC&#xff08;片上…

Excel学习05

员工排班表 通过之前的学习&#xff0c;目前的我们已经具备了Excel的初步制作能力。接下来将从实际出发制作一个员工排班表。在制作排版表之前&#xff0c;先来看我们要用到的函数。 DATE函数 date函数是Excel中处理日期的核心函数之一&#xff0c;它能够将单独的年、月、日…

黑马JVM解析笔记(五):深入理解Java字节码执行机制

1.从字节码的角度分析i /** * 从字节码角度分析 a 相关题目 */ public class Demo3_2 {public static void main(String[] args) {int a 10;int b a a a--;System.out.println(a);System.out.println(b);} }a 和 a 实际上代表了两个不同的操作&#xff0c;它们分别对应自增…

从社交媒体到金融“超级应用”,马斯克X平台将上线投资交易服务

报道称&#xff0c;马斯克旗下的X平台将推出“超级App”&#xff0c;提供投资和交易服务&#xff0c;另外&#xff0c;X也在探索引入信用卡或借记卡。作为金融服务布局的第一步&#xff0c;X平台已宣布将推出X Money——一项数字钱包和点对点支付服务&#xff0c;Visa将成为其首…

【入门第2课】Splunk数据接入

前言 Splunk支持多种多样的数据源,比如它可以直接上传文件,可以监控本地的任何目录或文件,也可以配置通用转发器等方式来完成数据接入。Splunk所有的设置都可以通过Web页面、使用Splunk CLI命令,甚至是直接修改配置文件,以此来完成设置。 那么,如何接入数据呢?我们通过…