携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
每日刷题 2022.08.16
- leetcode原题链接:leetcode.cn/problems/im…
- 难度:中等
- 方法: 字典树
题目
- 设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
- 实现 MagicDictionary 类:
- MagicDictionary() 初始化对象
- void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
- bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false 。
示例
输入
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
输出
[null, null, false, true, false, false]
解释
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False
提示
- 1 <= dictionary.length <= 100
- 1 <= dictionary[i].length <= 100
- dictionary[i] 仅由小写英文字母组成
- dictionary 中的所有字符串 互不相同
- 1 <= searchWord.length <= 100
- searchWord 仅由小写英文字母组成
- buildDict 仅在 search 之前调用一次
- 最多调用 100 次 search
解题思路
- 初次学习
Trie树,又被叫做:字典树或前缀树,是一种用于快速查找某个字符串/字符前缀是否存在的数据结构。 - 核心:使用边代表有无字符(可以直接将其字符设置为属性名,这样就比较好查找),使用点表示是否为“单词结尾”以及“其后续字符串的字符是什么”
- 目前使用过两种实现字典树的方法。
// 第一种:创建一个新的结构体的节点
function Trie(val, isEnd) {
this.val = val;
this.child = [];
this.isEnd = isEnd || false;
}
// 最终的输出样式:
Trie: {
val: '13',
child: [
{
val: '456',
child: [],
isEnd: true
}
],
isEnd: false
}
// 第二种
let root = {};
!root[属性名] => 这是对象使用括号访问属性的方式
// 最终的输出样式:
{
'0': {
'1': {},
'0': {}
}
}
- 第一种和第二种创建的方式,比较起来,还是第二种更符合字典树的定义。将边表示字符,那么对应的就应该将对象中的属性名设置为当前的字符,表示当前的字符和上一个字符存在一条边(当对象中不存在任何属性名时,则表示其没有连接的边)。
- 字典树一般包含:创建、插入、搜索三个方法。这道题的搜索比较麻烦,借助了
dfs。 - 搜索:将每一个节点都当做是一个根节点,也就是处于字典树中的每一个字符都当作其是根节点来处理,递归调用有返回值的
dfs。因为每一层的逻辑是类似的,只是需要多次遍历。(根据题目可知:只能将字符串中的一个字母换成另一个字母,替换不是增加或者删除)dfs需要的参数:根节点root、当前字符串cur、层数pos、标记是否被修改过的变量isModify。dfs的终止条件:pos = cur.length当深度等于当前的字符串的长度的时候,就说明已经查找结束,此时需要判断字典树中的当前字符是否为结束单词isEnd并且还需要满足isModify = true,也就是两个字符串的长度相等且被修改过一次即可。dfs的内部实现逻辑:如果root是包含cur[pos]字符的,那么就可以直接改变参数,往下一层找。(因为root包含:表示两个字符是相同的)如果往下遍历返回的是true,那么就可以直接向上返回true。- 反之如果返回的是
false,表示往下遍历找完了字符,下面的都不符合要求(要么isEnd或者isModify不为true),需要走另一条路,换一个字符。那么就需要修改isModify = true,记录下这次修改操作,接着找到当前的字典树root的下一个字符,再从这个字符往下遍历。同样的如果从当前的一直遍历下去,返回的是true,则向上返回true;否则就跳出循环返回false(因为无论如何都没有办法成功)。
字典树应用
- (困难)745. 前缀和后缀搜索通过双向建树,求解。
- (中等)剑指 Offer II 067. 最大的异或不常见的字典树的应用题目,通常都是将字符串中的字符插入到字典树中,而本题是将字典序中的二进制位插入到字典树中。
最近学习的常用的位运算
- 判断数组中是存在元素
let arr = []; arr.length === 0 - 判断对象中是否存在属性:
Object.keys(node).length === 0
AC代码
function Trie(val, isEnd) {
this.val = val;
this.child = [];
this.isEnd = isEnd || false;
}
var MagicDictionary = function() {
this.trie = new Trie();
};
/**
* @param {string[]} dictionary
* @return {void}
*/
MagicDictionary.prototype.buildDict = function(dictionary) {
for(let dic of dictionary) {
this.insert(dic);
}
};
MagicDictionary.prototype.insert = function (dic) {
// 对于每一个字符串,进行操作
let tr = this.trie;
for(const one of dic) {
if(!tr.child[one]) tr.child[one] = new Trie(one);
tr = tr.child[one];
}
tr.isEnd = true;
}
/**
* @param {string} searchWord
* @return {boolean}
*/
MagicDictionary.prototype.search = function(searchWord) {
// 搜索的单词
// dfs需要的参数,需要搜索的字符串,当前搜索的位置、是否已经有一个被替换掉
let len = searchWord.length;
return this.dfs(this.trie, searchWord, len, 0, false);
};
MagicDictionary.prototype.dfs = function (tree, search, n, pos, isModifity) {
if(pos == n){
// 需要是结尾,并且仅只修改了一个元素
return tree.isEnd && isModifity;
}
// 没有找到结尾的时候,的操作
// 需要判断当前的元素是否在tree的孩子数组中
let cur = search[pos];
if(tree.child[cur]) {
// 存在的时候就继续调用, 不存在被修改的一个元素
if(this.dfs(tree.child[cur], search, n, pos + 1, isModifity)) {
return true;
}
}
if(!isModifity) {
// 不存在的时候, 需要将当前的modifity修改,但是要判断是否还可以修改
// 遍历26个字母,找出哪个是其的下一个字母,然后修改modify再进行递归
let a = 'a'.charCodeAt();
for(let i = 0; i < 26; i++) {
let modi = String.fromCharCode(i + a);
if(modi != cur && tree.child[modi]) {
// 存在的话,就找到可以使用的子节点
if(this.dfs(tree.child[modi], search, n, pos + 1, true)) {
return true;
}
}
}
}
return false;
}
/**
* Your MagicDictionary object will be instantiated and called as such:
* var obj = new MagicDictionary()
* obj.buildDict(dictionary)
* var param_2 = obj.search(searchWord)
*/