实现一个魔法字典

168 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

说在前面

🎈不知道大家对于算法的学习是一个怎样的心态呢?为了面试还是因为兴趣?不管是处于什么原因,算法学习需要持续保持,今天让我们一起来看看这一道题目————实现一个魔法字典

题目描述

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。

实现 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树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

  • 深度优先搜索

深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。

知道了要使用到的知识点之后,我们可以进一步思考如何将它们运用到这道题目的解答上。

构建字典树

字典树的结构如下图,每一个节点保存一个字母,同一单词的字母前后连接,从根节点到包含isEnd的节点代表一个单词,下图中的字典树中包含两个单词:appapple

image.png

我们可以先将所有单词保存到一颗字典树中,便于后续搜索查找,遍历单词的每一个字母,将字母保存到对应层级中,并在单词最后一个字母加上标志,具体代码如下:

MagicDictionary.prototype.buildTree = function(dictionary) {
    dictionary.map(item=>{
        let tree = this.tree;
        for(const ch of item){
            if(!tree[ch]) tree[ch] = {};
            tree = tree[ch];
        }
        tree.isEnd = true;
    })
};

深度优先搜索

题目是这样说的,如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。也就是说要我们判断给出的单词可不可以通过改变一个字母使其与字典中的单词相同,也就是判断字典中是否有和给出单词只差一个字母的单词。

我们可以尝试替换给出单词的每一个字母,通过先前构建的字典树搜索判断替换后的单词是否存在,使用canReplace表示当前单词是否已经进行过替换,在字典树中遍历查找给出的单词,遇到不存在的节点且canReplace为true时将canReplace设置为false,递归搜索下一个字母节点,直到找到可替换的单词或者遍历到单词的最后一个字母,具体代码如下:

MagicDictionary.prototype.judge = function(searchWord,i,canReplace = true,tree = this.tree) {
    const ch = searchWord[i];
    if(canReplace){
        for(const k in tree){
            if(i == searchWord.length - 1){
                if(tree[k].isEnd && k != ch) return true;
            }else{
                if(this.judge(searchWord,i+1,k == ch,tree[k])) return true;
            }
        }
        return false;
    }else{
        if(tree[ch]){
            if(i == searchWord.length - 1) return tree[ch].isEnd == true;
            return this.judge(searchWord,i+1,canReplace,tree[ch]);
        }
        return false;
    }
};

AC代码

var MagicDictionary = function() {
    this.tree = {};
};

/** 
 * @param {string[]} dictionary
 * @return {void}
 */
MagicDictionary.prototype.buildDict = function(dictionary) {
    this.dictionary = dictionary;
    this.buildTree(dictionary);
};

MagicDictionary.prototype.buildTree = function(dictionary) {
    dictionary.map(item=>{
        let tree = this.tree;
        for(const ch of item){
            if(!tree[ch]) tree[ch] = {};
            tree = tree[ch];
        }
        tree.isEnd = true;
    })
};

MagicDictionary.prototype.judge = function(searchWord,i,canReplace = true,tree = this.tree) {
    const ch = searchWord[i];
    if(canReplace){
        for(const k in tree){
            if(i == searchWord.length - 1){
                if(tree[k].isEnd && k != ch) return true;
            }else{
                if(this.judge(searchWord,i+1,k == ch,tree[k])) return true;
            }
        }
        return false;
    }else{
        if(tree[ch]){
            if(i == searchWord.length - 1) return tree[ch].isEnd == true;
            return this.judge(searchWord,i+1,canReplace,tree[ch]);
        }
        return false;
    }
};
/** 
 * @param {string} searchWord
 * @return {boolean}
 */
MagicDictionary.prototype.search = function(searchWord) {
    return this.judge(searchWord,0);
};

/**
 * Your MagicDictionary object will be instantiated and called as such:
 * var obj = new MagicDictionary()
 * obj.buildDict(dictionary)
 * var param_2 = obj.search(searchWord)
 */

说在后面

🎉这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业一年,两年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。