壹 ❀ 引

本题来自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的暴力做法

题目本意是想让我们通过类实现出一种名叫前缀树的数据结构,并通过类附带的一些方法,实现字符串插入,字符串搜索,以及字符串前缀检查相关功能,综合来说就是下面三个方法(都需要你自己来实现它):

  1. 前缀树会附带一个insert方法,调用此方法可以像前缀树结构中插入一个字符。
  2. 前缀树会附带一个search方法,调用此方法可以在前缀树中检索是否存在某个字符。
  3. 前缀树会附带一个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个基本性质:

根节点不包含字符,除根节点外到每个节点的路径都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。-----百度百科

我们提炼下信息,根节点不包含字符,从根节点到子节点的边都只包含一个字符,每个节点紧挨着的子节点的边所包含的字符都不相同(相同就复用了)。其次,前缀树的节点还用于记录是否为一个字符串的结尾。

比如上图中存在的字符串有hoosbbchehelheg,红色表示一个单词的结尾,也就是说从根节点到这个红色节点的边是一个完整的需要记录的字符串,比如像hehel它们部分前缀相同,这里就起到了复用的目的。

很明显,如果我们记录的是小写字符串,从根节点出发的第一条路径就有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 (前缀树) 题解分析,第一次了解前缀树(字典树)的更多相关文章

  1. 字典树(查找树) leetcode 208. Implement Trie (Prefix Tree) 、211. Add and Search Word - Data structure design

    字典树(查找树) 26个分支作用:检测字符串是否在这个字典里面插入.查找 字典树与哈希表的对比:时间复杂度:以字符来看:O(N).O(N) 以字符串来看:O(1).O(1)空间复杂度:字典树远远小于哈 ...

  2. [LeetCode] 208. Implement Trie (Prefix Tree) 实现字典树(前缀树)

    Implement a trie with insert, search, and startsWith methods. Example: Trie trie = new Trie(); trie. ...

  3. Java实现 LeetCode 208 实现 Trie (前缀树)

    208. 实现 Trie (前缀树) 实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作. 示例: Trie trie = new Trie() ...

  4. [leetcode] 208. 实现 Trie (前缀树)(Java)

    208. 实现 Trie (前缀树) 实现Trie树,网上教程一大堆,没啥可说的 public class Trie { private class Node { private int dumpli ...

  5. [LeetCode] 208. Implement Trie (Prefix Tree) ☆☆☆

    Implement a trie with insert, search, and startsWith methods. Note:You may assume that all inputs ar ...

  6. leetcode 208. 实现 Trie (前缀树)

    实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作. 示例: Trie trie = new Trie(); trie.insert(" ...

  7. LeetCode 208 Implement Trie (Prefix Tree) 字典树(前缀树)

    Implement a trie with insert, search, and startsWith methods.Note:You may assume that all inputs are ...

  8. leetcode@ [208] Implement Trie (Prefix Tree)

    Trie 树模板 https://leetcode.com/problems/implement-trie-prefix-tree/ class TrieNode { public: char var ...

  9. 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 ...

  10. Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)

    745. 前缀和后缀搜索 给定多个 words,words[i] 的权重为 i . 设计一个类 WordFilter 实现函数WordFilter.f(String prefix, String su ...

随机推荐

  1. Redis 哨兵模式高可用

    本文为博主原创,未经允许不得转载: 目录: 1. 哨兵 Sentinel 介绍 2. 哨兵架构特点及工作原理 3. redis哨兵架构搭建步骤 4. 哨兵数据丢失 5. spring boot 整合  ...

  2. MySQL的SQL优化常用30种方法[转]

    MySQL的SQL优化常用30种方法 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中使用!=或< ...

  3. SpringBoot01:HelloWorld!

    回顾Spring Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架. Spring是为了解决企业级应用开发的复杂性而创建的,简化开发. Spring是怎样简化Java开发的呢? ...

  4. [转帖]Linux cache参数调优

    https://zhuanlan.zhihu.com/p/136237953 缓存机制(cache)是保证Linux环境下对硬盘/flash操作效率的有效方式.cache建立在内存中,它缓存了硬盘/f ...

  5. [转帖]Jmeter之JDBC Request使用方法(oracle)

    https://zhuanlan.zhihu.com/p/121747788 JDBC Request: 这个sampler可以向数据库发送一个jdbc请求(sql语句),它经常需要和JDBC Con ...

  6. [转帖]CygWin、MingW、MSYS之间的关系

    https://www.jianshu.com/p/09198f6e0a3c 前言 在跨平台开发或移植中,经常会听说Cygwin.MingW.MSYS,他们之间是什么关系?对于将要完成的任务,应该选择 ...

  7. BAdI:INVOICE_UPDATE 导致MM Invoice Doc. Missing

    Symptom:发票校验过程中,对应发票号生成,FI凭证也产生,但是对应RBKP,RSEG中无相应的发票. 原先这一问题SAP早给出过解释,参照note:1876234 Cause:在SD MM模块中 ...

  8. VictoriaMetrics 1.84.0发布

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 11.25日,valyala大神发布了VictoriaMe ...

  9. Fabric网络升级(二)

    原文来自这里. 如果想了解最新版Fabric的特殊事项,详见Upgrading to the latest release of Fabric. 本章只介绍更新Fabric组件的操作.关于如何通过编辑 ...

  10. Go 泛型发展史与基本介绍

    Go 泛型发展史与基本介绍 Go 1.18版本增加了对泛型的支持,泛型也是自 Go 语言开源以来所做的最大改变. 目录 Go 泛型发展史与基本介绍 一.为什么要加入泛型? 二.什么是泛型 三.泛型的来 ...