JS Leetcode 208. 实现 Trie (前缀树) 题解分析,第一次了解前缀树(字典树)
壹 ❀ 引
本题来自LeetCode 208. 实现 Trie (前缀树),难度中等,题目描述如下:
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 仅由小写英文字母组成
- insert、search 和 startsWith 调用次数 总计 不超过 3 * 104 次
让我们提取下题目信息,简单分析题意,然后实现它。
贰 ❀ 借用数组API的暴力做法
题目本意是想让我们通过类实现出一种名叫前缀树的数据结构,并通过类附带的一些方法,实现字符串插入,字符串搜索,以及字符串前缀检查相关功能,综合来说就是下面三个方法(都需要你自己来实现它):
- 前缀树会附带一个
insert方法,调用此方法可以像前缀树结构中插入一个字符。 - 前缀树会附带一个
search方法,调用此方法可以在前缀树中检索是否存在某个字符。 - 前缀树会附带一个
startWith方法,调用此方法可以检索前缀树中是否有某个字符串的开头等于一个提供的字符串。
需要注意的是第三条,题目原话是如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true,我理解的之前已经插入是最后插入的单词,其实题目指的是之前所有插入过的单词,然后查找有没有符合条件的字符串。
OK,我们分析完题目要求,老实说,我第一想到的是借用数组API,比如插入我们可以在数组头部插入,查找可以使用indexOf,而检索字符串前缀同样可以借用startsWith方法,直接上代码:
/**
* Initialize your data structure here.
*/
var Trie = function () {
this.trie = [];
};
/**
* Inserts a word into the trie.
* @param {string} word
* @return {void}
*/
Trie.prototype.insert = function (word) {
this.trie.unshift(word)
};
/**
* Returns if the word is in the trie.
* @param {string} word
* @return {boolean}
*/
Trie.prototype.search = function (word) {
return this.trie.indexOf(word) > -1;
};
/**
* Returns if there is any word in the trie that starts with the given prefix.
* @param {string} prefix
* @return {boolean}
*/
Trie.prototype.startsWith = function (prefix) {
if (this.trie.length > 0) {
return this.trie.some(s => s.startsWith(prefix))
};
return false;
};
这里的代码就不解释什么了,因为比较简单,直接按照题目的意思去调用对应API即可。虽然可以实现,但很显然,这并不是什么好的做法。
贰 ❀ 字典树(前缀树)
根据题意来说,前缀树是一种高效存储和检索字符串的数据结构,但对于数组而言,按下标访问时间复杂度为O(1),但如果不知道下标是去访问想要的元素,时间复杂度直接到了O(n)了,因为运气不好可能最后一个元素才是我们想要的,自然不适合来实现前缀树。
那么既然要实现前缀树,我们总得知道这是个啥玩意,比如我之前完全就没听说过....
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
它有3个基本性质:
根节点不包含字符,除根节点外到每个节点的路径都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。-----百度百科
我们提炼下信息,根节点不包含字符,从根节点到子节点的边都只包含一个字符,每个节点紧挨着的子节点的边所包含的字符都不相同(相同就复用了)。其次,前缀树的节点还用于记录是否为一个字符串的结尾。

比如上图中存在的字符串有ho,os,b,bc,he,hel,heg,红色表示一个单词的结尾,也就是说从根节点到这个红色节点的边是一个完整的需要记录的字符串,比如像he,hel它们部分前缀相同,这里就起到了复用的目的。
很明显,如果我们记录的是小写字符串,从根节点出发的第一条路径就有a-z一共26种可能,之后每个节点如果还有子节点同理,当然如果我们记录的是数字那就是0-910中可能。
抽象点来理解,当我们要记录一个字符串,第一个字符可能是26个字母中的其中一个,如果当前路径没创建,我们就创建好这条路径,如果这个字符串恰好就只有一个字母,那我们还得在这个子节点上做个标记,表示到这为止有个完整的字符串。
而当记录其它字符串时,我们还是一样的操作,第一个字母我们得看看之前有创建相同的路径吗?如果有就不要反复创建,如果没有就额外单独创建一条,大概就是这么个思路了。
OK,为了达到更高效的访问查找效率,所以这里肯定是得借助字典(对象)以Key(a-z)的形式来记录字符。比如我们如果要记录一个单词是app,那么它的结果应该是这样:
let dic = {
a:{
p:{
p:{
end:true
}
}
}
}
让我们来尝试实现它,这段逻辑会比较绕,可能需要大家自己断点理解下:
var Trie = function() {
this.dic = {}
};
Trie.prototype.insert = function(word) {
// 这里比较巧妙,耐心点看
// 浅拷贝
var dic = this.dic
// 遍历要插入的字符串的每个字符
for (var w of word) {
// 如果之前没创建过,那就以此字符创建成一个空对象
if(!dic[w]){
// 为对象添加属性,因为是浅拷贝,所以也会影响到this.dic
dic[w] = {}
};
// 取到这个字符的属性,为下一个字符做准备,注意这里是直接修改dic的引用,相当于被重新赋值了
dic = dic[w]
}
dic.end = true;
};
Trie.prototype.prefix = function(word) {
var dic = this.dic
for (var w of word) {
if (!dic[w]){
// 如果有一个字符直接没访问到,那就中断查找,说明输入的字符不存在,且不会是某个字符串的开头
return false
};
dic = dic[w]
};
// 相当于一只找到了最后一个字符的对象, 如果它是一个完整单词的结果,那么一定会有end属性
return dic;
}
Trie.prototype.search = function(word, h) {
var res = this.prefix(word);
// 可能返回的是一个空对象,所以需要效验这个对象的end属性是不是true,注意不要写成了return res && res.end
// 因为即便一个一个空对象也是能访问end属性的,只是值为undefined,我就踩了这个坑提交挂了
return res && res.end === true;
};
Trie.prototype.startsWith = function(prefix) {
return this.prefix(prefix);
};
那么本文就到这里了。
JS Leetcode 208. 实现 Trie (前缀树) 题解分析,第一次了解前缀树(字典树)的更多相关文章
- 字典树(查找树) leetcode 208. Implement Trie (Prefix Tree) 、211. Add and Search Word - Data structure design
字典树(查找树) 26个分支作用:检测字符串是否在这个字典里面插入.查找 字典树与哈希表的对比:时间复杂度:以字符来看:O(N).O(N) 以字符串来看:O(1).O(1)空间复杂度:字典树远远小于哈 ...
- [LeetCode] 208. Implement Trie (Prefix Tree) 实现字典树(前缀树)
Implement a trie with insert, search, and startsWith methods. Example: Trie trie = new Trie(); trie. ...
- Java实现 LeetCode 208 实现 Trie (前缀树)
208. 实现 Trie (前缀树) 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作. 示例: Trie trie = new Trie() ...
- [leetcode] 208. 实现 Trie (前缀树)(Java)
208. 实现 Trie (前缀树) 实现Trie树,网上教程一大堆,没啥可说的 public class Trie { private class Node { private int dumpli ...
- [LeetCode] 208. Implement Trie (Prefix Tree) ☆☆☆
Implement a trie with insert, search, and startsWith methods. Note:You may assume that all inputs ar ...
- leetcode 208. 实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作. 示例: Trie trie = new Trie(); trie.insert(" ...
- LeetCode 208 Implement Trie (Prefix Tree) 字典树(前缀树)
Implement a trie with insert, search, and startsWith methods.Note:You may assume that all inputs are ...
- leetcode@ [208] Implement Trie (Prefix Tree)
Trie 树模板 https://leetcode.com/problems/implement-trie-prefix-tree/ class TrieNode { public: char var ...
- Java for LeetCode 208 Implement Trie (Prefix Tree)
Implement a trie with insert, search, and startsWith methods. Note: You may assume that all inputs a ...
- Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
745. 前缀和后缀搜索 给定多个 words,words[i] 的权重为 i . 设计一个类 WordFilter 实现函数WordFilter.f(String prefix, String su ...
随机推荐
- Mysql 开启慢日志查询及查看慢日志 sql
本文为博主原创,转载请注明出处: 目录: 1.Mysql 开启慢日志配置的查询 2. 通过sql 设置Mysql 的慢日志开启 3. 通过慢 sql 日志文件查看慢 sql 1.M ...
- Linux 中常见目录的作用
by emanjusaka from https://www.emanjusaka.top/2024/01/linux-directory-role 彼岸花开可奈何 本文欢迎分享与聚合,全文转载请留下 ...
- 基于python+django的求职招聘网站-网上招聘管理系统设计与实现
该系统是基于python+django的求职招聘网站.网上招聘管理系统.网上人才招聘系统.毕业生求职招聘系统.大学生求职招聘系统.校园招聘系统.企业招聘系统.系统适合场景:大学生.课程作业.毕业设计. ...
- 01-Linux命令和C语言基础
1 Linux开发环境搭建 1.1 虚拟机安装 1.安装VM Ware 2.安装ubuntu 分区 -- Linux没有盘符的概念 / -- 5000M /boot -- 系统启动过程中读取的重要文件 ...
- 基于AHB_BUS SRAM控制器的设计-02
AHB-SRAMC Design 片选信号决定哪几个memory被选择和功耗 sram_addr和sram_wdata都是可以通过AHB总线的控制信号得到的 1. sram_csn信号理解 hsize ...
- 【C++】枚举作为类函数返回值时需定义在使用之前
枚举定义在前,作为函数返回值在后 枚举定义在后,则函数返回值需用普通类型
- 【TouchGFX 】使用 CubeMX 创建 TouchGFX 工程时 LCD 死活不显示
生成的代码死活无法让LCD显示,经两个晚上的分析验证是LTDC_CLK引脚速度设置为低速导致,经测试中速.高速.超高速都正常,真是冤,聊以此以示纪念
- 【canvas】 绘制七巧板
效果图: 代码 : <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- [转帖]058、集群优化之PD
PD调度基本概念 调度流程 调度中还有这还缺来了merge,例如合并空region. store: 基本信息,容量,剩余空间,读写流量等 region: 范围,副本分布,副本状态,数据量,读写流量等 ...
- [转帖]nginx上传模块—nginx upload module-
https://www.cnblogs.com/lidabo/p/4171515.html 一. nginx upload module原理 官方文档: http://www.grid.net.ru/ ...