class MagicDictionary:
def __init__(self):
self.words_by_length = {}
def buildDict(self, dictionary: List[str]) -> None:
for word in dictionary:
l = len(word)
if l not in self.words_by_length:
self.words_by_length[l] = []
self.words_by_length[l].append(word)
def search(self, searchWord: str) -> bool:
l = len(searchWord)
if l not in self.words_by_length:
return False
for candidate in self.words_by_length[l]:
diff = 0
for a, b in zip(candidate, searchWord):
if a != b:
diff += 1
if diff > 1:
break
if diff == 1:
return True
return False
class MagicDictionary {
public:
unordered_map<int, vector<string>> words_by_length;
MagicDictionary() {}
void buildDict(vector<string>& dictionary) {
for (const string& word : dictionary) {
words_by_length[word.size()].push_back(word);
}
}
bool search(string searchWord) {
int l = searchWord.size();
if (words_by_length.find(l) == words_by_length.end()) return false;
for (const string& candidate : words_by_length[l]) {
int diff = 0;
for (int i = 0; i < l; ++i) {
if (candidate[i] != searchWord[i]) {
diff++;
if (diff > 1) break;
}
}
if (diff == 1) return true;
}
return false;
}
};
class MagicDictionary {
private Map<Integer, List<String>> wordsByLength;
public MagicDictionary() {
wordsByLength = new HashMap<>();
}
public void buildDict(String[] dictionary) {
for (String word : dictionary) {
int l = word.length();
wordsByLength.computeIfAbsent(l, k -> new ArrayList<>()).add(word);
}
}
public boolean search(String searchWord) {
int l = searchWord.length();
if (!wordsByLength.containsKey(l)) return false;
for (String candidate : wordsByLength.get(l)) {
int diff = 0;
for (int i = 0; i < l; i++) {
if (candidate.charAt(i) != searchWord.charAt(i)) {
diff++;
if (diff > 1) break;
}
}
if (diff == 1) return true;
}
return false;
}
}
var MagicDictionary = function() {
this.wordsByLength = {};
};
MagicDictionary.prototype.buildDict = function(dictionary) {
for (let word of dictionary) {
let l = word.length;
if (!(l in this.wordsByLength)) {
this.wordsByLength[l] = [];
}
this.wordsByLength[l].push(word);
}
};
MagicDictionary.prototype.search = function(searchWord) {
let l = searchWord.length;
if (!(l in this.wordsByLength)) return false;
for (let candidate of this.wordsByLength[l]) {
let diff = 0;
for (let i = 0; i < l; i++) {
if (candidate[i] !== searchWord[i]) {
diff++;
if (diff > 1) break;
}
}
if (diff === 1) return true;
}
return false;
};
You are asked to design a data structure called MagicDictionary that supports two main operations:
buildDict(dictionary): Build the dictionary from a list of unique words.search(searchWord): Return true if there is any word in the dictionary that differs from searchWord by exactly one character (i.e., you can change exactly one letter in searchWord to get a word from the dictionary). Otherwise, return false.search queries after building the dictionary.
At first glance, the problem seems to invite a brute-force approach: for each search, compare the searchWord to every word in the dictionary, count the number of differing characters, and return true if exactly one character differs. However, this is inefficient if the dictionary is large or if there are many queries.
To optimize, we notice a few key points:
searchWord to words of the same length, reducing unnecessary comparisons.
Here is a step-by-step breakdown of how to implement the Magic Dictionary efficiently:
dictionary, append it to the list associated with its length in the map.searchWord, look up the list of dictionary words with the same length.searchWord character by character.true.false.L takes O(L) time, and only a subset of words need to be checked for each query.
Let's walk through an example:
Build Dictionary: ["hello", "hallo", "leetcode"]
Search: "hello"
["hello", "hallo"]true since one valid word exists."hhllo"
["hello", "hallo"]true"hell"
false."leetcodd"
["leetcode"]falseBrute-force Approach:
searchWord to every word in the dictionary.N is the number of words, and L is the average word length, time per search: O(N * L).M be the number of words of the same length as searchWord. Time per search: O(M * L).The Magic Dictionary problem challenges us to efficiently find words that differ by exactly one character. By grouping dictionary words by their length, we reduce unnecessary comparisons and make each search operation much faster. The solution is elegant because it leverages simple data structures (hash maps and lists) to optimize both the build and search processes, making it practical for real-world use where many queries are expected.