The problem
Design a data structure that supports adding new words and finding if a string matches any previously added string.
Implement the WordDictionary class:
WordDictionary()Initializes the object.void addWord(word)Addswordto the data structure; it can be matched later.bool search(word)Returnstrueif there is any string in the data structure that matcheswordorfalseotherwise.wordmay contain dots ('.'), where dots can be matched with any letter.
Examples
Input
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
Output
[null,null,null,null,false,true,true,true]
Explanation
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // return False
wordDictionary.search("bad"); // return True
wordDictionary.search(".ad"); // return True
wordDictionary.search("b.."); // return True
Constraints
1 <= word.length <= 25wordinaddWordconsists of lowercase English letters.wordinsearchconsists of'.'or lowercase English letters.- There will be at most 2 dots in
wordforsearchqueries. - At most
10^4calls will be made toaddWordandsearch.
Brute force solution
class WordDictionary {
private var store: [String]
init() {
self.store = []
}
func addWord(_ word: String) {
self.store.append(word)
}
func search(_ word: String) -> Bool {
let wordArray = Array(word)
let wordCount = wordArray.count
for w in self.store {
let wArray = Array(w)
let wCount = wArray.count
if wCount != wordCount {
continue
}
var i = 0
while i < wCount {
if wArray[i] == wordArray[i] || wordArray[i] == "." {
i += 1
} else {
break
}
if i == wCount {
return true
}
}
}
return false
}
}
Explanation
We can solve this problem in a brute-force way by using an array.
It is not the most efficient solution because the search operation will take O(m * n) time. We can optimize it by using a Trie (Prefix Tree) data structure.
- For the
addWordmethod, we will use theappendoperation, which takes O(1) time. - For
search, we need a little more effort because we cannot only search for a specificword, but the input may contain dots ('.'), which can be matched with any letter. For example, if we have input["c", "d"]and we are looking forโ.dโ, we will iterate through each element instoreand compare it with the inputwordcharacter by character, also checking if a character can be..
Time/space complexity
- Time complexity: O(1) for
addWord, and O(m * n) forsearch. - Space complexity: O(m * n),
wheremis the number of words added to the array andnis the length of the word.
Trie (Prefix Tree) solution
final class TrieNode {
var children: [Character: TrieNode?]
var endOfWord: Bool
init() {
self.children = [:]
self.endOfWord = false
}
}
class WordDictionary {
private let root: TrieNode
init() {
self.root = TrieNode()
}
func addWord(_ word: String) {
var curr = self.root
for c in word {
if curr.children[c] == nil {
curr.children[c] = TrieNode()
}
curr = curr.children[c]!!
}
curr.endOfWord = true
}
func search(_ word: String) -> Bool {
func dfs(_ j: Int, _ node: TrieNode) -> Bool {
var curr = node
let wordArray = Array(word)
let wordCount = wordArray.count
for i in j ..< wordCount {
let c = wordArray[i]
if c == "." {
for child in curr.children.values {
if let childNode = child, dfs(i + 1, childNode) {
return true
}
}
return false
} else {
if curr.children[c] == nil {
return false
}
curr = curr.children[c]!!
}
}
return curr.endOfWord
}
return dfs(0, self.root)
}
}
Explanation
We can solve this problem in a more optimal way by using a Trie (Prefix Tree) data structure. It will help us decrease the time complexity for search from O(m * n) to O(n).
To do that, we need an additional class TrieNode that will store children nodes by character and an endOfWord property that will tell us when a sequence of characters forms a complete word.
- For
addWord, we iterate through all characters in the inputwordand mark it as theendOfWord. - For
search, we need to add a depth-first search (DFS) helper (backtracking). This allows us to search for a match in every node when we have.in the input because.matches any character.
For example, when wesearchfor.adand we have["bad", "dad", "mad"]as added words, we returntruein all these cases because.admatches all of them.
This solution is more efficient because when we find a match, we return true and stop iterating, using recursion (DFS).
Time/space complexity
- Time complexity: O(n) for
addWordandsearch. - Space complexity: O(t + n),
wherenis the length of the word andtis the total number ofTrieNodes created in the Trie.