每日一刷经验分享:676中等. 实现一个魔法字典

108 阅读4分钟

676中等. 实现一个魔法字典

题意

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

实现 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]

解释

  1. MagicDictionary magicDictionary = new MagicDictionary();
  2. magicDictionary.buildDict(["hello", "leetcode"]);
  3. magicDictionary.search("hello"); // 返回 False
  4. magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
  5. magicDictionary.search("hell"); // 返回 False
  6. 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

题型:哈希表设计字典树字符串

AC代码

👀Java版本: 🥖方法一:Hash+数组

class MagicDictionary {
    Set<String> set;
    public MagicDictionary() {
        set = new HashSet<>();
    }
    public void buildDict(String[] dictionary) {
        for(String s:dictionary) set.add(s);
    }
    public boolean search(String searchWord) {
        char[] chs = searchWord.toCharArray();
        int len = chs.length;
        char c;
        for(int i=0;i<len;i++){
            c = chs[i];
            for(int j=0;j<26;j++){
                chs[i]=(char)('a'+j);
                if(chs[i]==c) continue;
                // System.out.println(chs.toString()); 
                if(set.contains(String.valueOf(chs))) return true;
            }
            chs[i]=c;
        }
        return false;
    }
}

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

🥖方法二(前缀树解决方法):

思路分析: 涉及到在字典中搜索单词的问题,一般前缀树的的结构能高效的解决问题。 此题也是在单词组中搜索单词,蛋试需要搜索与word相差一个字母的单词,就是难度稍微增大了一点点。


//前缀树的程序表示
class TrieNode {
public:
    bool isWord;//当前节点为结尾是否是字符串
    vector<TrieNode*> children;
    TrieNode() : isWord(false), children(26, nullptr) {}
    ~TrieNode() {
        for (TrieNode* child : children)
            if (child) delete child;
    }
};

class MagicDictionary {
private:
    TrieNode *trieRoot;//构建的单词后缀树
    //在树中插入一个单词的方法实现
    void addWord(string &word) {
        TrieNode *ptr = trieRoot;//扫描这棵树,将word插入
        //将word的字符逐个插入
        for (auto ch : word) {
            if (ptr->children[ch - 'a'] == NULL) {
                ptr->children[ch - 'a'] = new TrieNode();
            }
            ptr = ptr->children[ch - 'a'];
        }
        ptr->isWord = true;//标记为单词
    }
    //在nowTreePtr中搜索word[index],isMod代表的是是否使用了替换一个字母的机会
    bool myFindWord(TrieNode *nowTreePtr, string &word, int index, bool isMod){
        if (nowTreePtr == NULL){
            return false;
        }
        if (word.size() == index){
            //此时搜索完毕,必须保证nowTreePtr也到达了一个单词的尾端,并且替换一个字母的机会也使用了
            return isMod && nowTreePtr->isWord;
        }
        else{
            //搜索nowTreePtr的26个节点
            for (int i = 0; i < 26; ++i){
                if (nowTreePtr->children[i] != NULL){
                    if ('a' + i == word[index]){
                        //成功匹配,继续搜索下一个字母
                        if (myFindWord(nowTreePtr->children[i], word, index + 1, isMod)){
                            return true;
                        }
                    }
                    else if (isMod == false && myFindWord(nowTreePtr->children[i], word, index + 1, true)){
                        //如果'a' + i != word[index],则使用替换字母的机会(在此之前替换字母的机会是没有使用的,因为只能使用一次)
                        return true;
                    }
                }
            }
            return false;
        }
    }
public:
    /** Initialize your data structure here. */
    MagicDictionary() {
        trieRoot = new TrieNode();
    }

    /** Build a dictionary through a list of words */
    void buildDict(vector<string> dict) {
        //构建字典树
        for (auto &word : dict){
            addWord(word);
        }
    }

    /** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */
    bool search(string word) {
        return myFindWord(trieRoot, word, 0, false);
    }
};

分析

从个人思路上看,要比对字符串想到的是使用Hash结构的类型存储,所以就在HashMap和Hashset之间进行比较因为在这道题中并不需要使用map存储对应的value,只需要记录是否存在,题目中也表明对应的字符串不重复,set虽然有去重功能,但是也不影响,因为本题的前置条件都是字符串没有重复的,其次Hash内部也是hash表进行实现的,所以选择使用HashSet进行字符串的存储。 把每一个字符串加入到set中,在遍历的时候每个字符串的每个字符换成其他字符然后去set中查找是否存在,找到之后直接返回True,直到遍历完成之后才会返回false。

题解过程分析

  1. set = new HashSet<>();设置对应的set,用于后期存储
  2. for(String s:dictionary) set.add(s); 添加字符串初始化到set中去
  3. char[] chs = searchWord.toCharArray(); 字符串转换为字符数组
  4. for(int i=0;i<len;i++) 遍历每一个字符
  5. for(int j=0;j<26;j++){chs[i]=(char)('a'+j);} 替换每一个字符为相应的字符之后查找是否在set中。
  6. if(set.contains(String.valueOf(chs))) return true; 如果存在返回true。

复杂度分析

  • 时间复杂度:O(n^2):时间主要耗费在查询上
  • 空间复杂度:O(n):空间主要耗费在存储上

总结

一般像是这种字符串类型的题目,往往可以从题意出发,因为这种题是要替换字符比对字符串相同,就是字典树或者哈希的处理,但是在字典树上去看,因为要替换字符,这个就比较麻烦,需要进行相应的处理,进行字典树的遍历。但是如果使用hash的方法的话,就可以使用就不用字典树处理的那么麻烦,直接修改字母然后根据set查询即可,因为Set使用的是HashSet,所以底层还是使用的Hash算法,这样的话再进行比对的时候就是求出对应的hash值进行比对。

  • 时间耗费原因解析:之所以时间上这么耗费,就是因为使用Set存储在每次查找的时候要每一个单词的每一个字母都进行替换进行比对,显然这样造成了时间上的浪费。

image.png

  • 空间耗费原因分析:空间的耗费因为Set底层使用的是Hash表存储的,所以相比于字典树存储差别不大

image.png