Implement a magic directory with buildDict
, and search
methods.
For the method buildDict
, you'll be given a list of non-repetitive words to build a dictionary.
For the method search
, you'll be given a word, and judge whether if you modify exactly one character into another character in this word, the modified word is in the dictionary you just built.
Example 1:
Input: buildDict(["hello", "leetcode"]), Output: Null Input: search("hello"), Output: False Input: search("hhllo"), Output: True Input: search("hell"), Output: False Input: search("leetcoded"), Output: False
Note:
a-z
.Intuition and Algorithm
\nCall two strings neighbors if exactly one character can be changed in one to make the strings equal (ie. their hamming distance is 1.)
\nStrings can only be neighbors if their lengths are equal. When search
ing a new word, let\'s check only the words that are the same length.
Python
\nclass MagicDictionary(object):\n def __init__(self):\n self.buckets = collections.defaultdict(list)\n\n def buildDict(self, words):\n for word in words:\n self.buckets[len(word)].append(word)\n\n def search(self, word):\n return any(sum(a!=b for a,b in zip(word, candidate)) == 1\n for candidate in self.buckets[len(word)])\n
Java
\nclass MagicDictionary {\n Map<Integer, ArrayList<String>> buckets;\n public MagicDictionary() {\n buckets = new HashMap();\n }\n\n public void buildDict(String[] words) {\n for (String word: words) {\n buckets.computeIfAbsent(word.length(), x -> new ArrayList()).add(word);\n }\n }\n\n public boolean search(String word) {\n if (!buckets.containsKey(word.length())) return false;\n for (String candidate: buckets.get(word.length())) {\n int mismatch = 0;\n for (int i = 0; i < word.length(); ++i) {\n if (word.charAt(i) != candidate.charAt(i)) {\n if (++mismatch > 1) break;\n }\n }\n if (mismatch == 1) return true;\n }\n return false;\n }\n}\n
Complexity Analysis
\nTime Complexity: to build and to search, where is the number of words
in our magic dictionary, is the total number of letters in it, and is the length of the search word.
Space Complexity: , the space used by buckets
.
Intuition
\nRecall in Approach #1 that two words are neighbors if exactly one character can be changed in one word to make the strings equal.
\nLet\'s say a word \'apple\' has generalized neighbors \'*pple\', \'a*ple\', \'ap*le\', \'app*e\', and \'appl*\'. When searching for whether a word like \'apply\' has a neighbor like \'apple\', we only need to know whether they have a common generalized neighbor.
\nAlgorithm
\nContinuing the above thinking, one issue is that \'apply\' is not a neighbor with itself, yet it has the same generalized neighbor \'*pply\'. To remedy this, we\'ll count how many sources generated \'*pply\'. If there are 2 or more, then one of them won\'t be \'apply\'. If there is exactly one, we should check that it wasn\'t \'apply\'. In either case, we can be sure that there was some magic word generating \'*pply\' that wasn\'t \'apply\'.
\nclass MagicDictionary(object):\n def _genneighbors(self, word):\n for i in xrange(len(word)):\n yield word[:i] + \'*\' + word[i+1:]\n\n def buildDict(self, words):\n self.words = set(words)\n self.count = collections.Counter(nei for word in words\n for nei in self._genneighbors(word))\n\n def search(self, word):\n return any(self.count[nei] > 1 or\n self.count[nei] == 1 and word not in self.words\n for nei in self._genneighbors(word))\n
public class MagicDictionary {\n Set<String> words;\n Map<String, Integer> count;\n\n public MagicDictionary() {\n words = new HashSet();\n count = new HashMap();\n }\n\n private ArrayList<String> generalizedNeighbors(String word) {\n ArrayList<String> ans = new ArrayList();\n char[] ca = word.toCharArray();\n for (int i = 0; i < word.length(); ++i) {\n char letter = ca[i];\n ca[i] = \'*\';\n String magic = new String(ca);\n ans.add(magic);\n ca[i] = letter;\n }\n return ans;\n }\n\n public void buildDict(String[] words) {\n for (String word: words) {\n this.words.add(word);\n for (String nei: generalizedNeighbors(word)) {\n count.put(nei, count.getOrDefault(nei, 0) + 1);\n }\n }\n }\n\n public boolean search(String word) {\n for (String nei: generalizedNeighbors(word)) {\n int c = count.getOrDefault(nei, 0);\n if (c > 1 || c == 1 && !words.contains(word)) return true;\n }\n return false;\n }\n}\n
Complexity Analysis
\nTime Complexity: to build and to search, where is the length of words[i]
, and is the length of our search word.
Space Complexity: , the space used by count
. We also use space when generating neighbors to search.
Analysis written by: @awice.
\n