Design Add and Search Words Data Structure
Design Add and Search Words Data Structure
Design a data structure to store words and efficiently support word addition and searching, including flexible matching.
Implement the WordDictionary class with the following methods:
- addWord(word): Adds the given word to the data structure, allowing it to be stored for future searches. This is useful for building a collection of words dynamically.
- search(word): Searches for a word in the data structure. The search supports both exact matches and partial matches using the . character as a wildcard. The . can represent any letter, making the search versatile for patterns or incomplete words.
This structure is particularly useful for applications like pattern matching or word suggestions.
["WordDictionary", "addWord", "day", "addWord", "bay", "addWord", "may", "search", "say", "search", "day", "search", ".ay", "search", "b.."]
Output:
[null, null, null, null, false, true, true, true]
Explanation:
- WordDictionary wordDictionary = new WordDictionary();
- wordDictionary.addWord(“day”);
- wordDictionary.addWord(“bay”);
- wordDictionary.addWord(“may”);
- wordDictionary.search(“say”); // return false
- wordDictionary.search(“day”); // return true
- wordDictionary.search(“.ay”); // return true
- wordDictionary.search(“b..”); // return true
Constraints:
- 1 <= word.length <= 20
- word in addWord consists of lowercase English letters.
- word in search consist of ‘.’ or lowercase English letters.
Design Add and Search Words Data Structure Solution
Recommendation for Time and Space Complexity – Aim for O(n) time per function call and O(t + n) space, where n is the word length and t is the number of nodes in the Trie.
Hints for solving problems
Hint 1 :
A brute force approach stores words in a list and searches through it, taking O(m * n) time. Consider a more efficient solution, like using a tree-like structure.
Hint 2 :
Use a Trie for efficient word insertion and search. For . (wildcard), recursively check all possible characters at that position.
Hint 3 :
While traversing the word:
- Match characters normally.
- For “.” explore all possible paths recursively.
- The complexity remains O((26^2) * n), as there are at most two . in a word.
There are mainly 2 approach to solve this problem-
- Brute Force Method
- Depth First Search(Trie) Method
1. Brute Force Method
Words are stored in a list, and each search checks all words by iterating through them, including handling wildcards (.) character by character. This leads to a time complexity of O(m * n), where m is the number of words and n is the word length.
- Time complexity: O(n) for addWord(), O(m∗n) for search().
- Space complexity: O(m*n)
where m is the number of words added and n is the length of the string.
Code
class WordDictionary {
public:
vector<string> store;
WordDictionary() {}
void addWord(string word) {
store.push_back(word);
}
bool search(string word) {
for (string w : store) {
if (w.length() != word.length()) continue;
int i = 0;
while (i < w.length()) {
if (w[i] == word[i] || word[i] == '.') {
i++;
} else {
break;
}
}
if (i == w.length()) {
return true;
}
}
return false;
}
};
public class WordDictionary {
private List<String> store;
public WordDictionary() {
store = new ArrayList<>();
}
public void addWord(String word) {
store.add(word);
}
public boolean search(String word) {
for (String w : store) {
if (w.length() != word.length()) continue;
int i = 0;
while (i < w.length()) {
if (w.charAt(i) == word.charAt(i) ||
word.charAt(i) == '.') {
i++;
} else {
break;
}
}
if (i == w.length()) {
return true;
}
}
return false;
}
}
class WordDictionary:
def __init__(self):
self.store = []
def addWord(self, word: str) -> None:
self.store.append(word)
def search(self, word: str) -> bool:
for w in self.store:
if len(w) != len(word):
continue
i = 0
while i < len(w):
if w[i] == word[i] or word[i] == '.':
i += 1
else:
break
if i == len(w):
return True
return False
class WordDictionary {
constructor() {
this.store = [];
}
/**
* @param {string} word
* @return {void}
*/
addWord(word) {
this.store.push(word);
}
/**
* @param {string} word
* @return {boolean}
*/
search(word) {
for (let w of this.store) {
if (w.length !== word.length) continue;
let i = 0;
while (i < w.length) {
if (w[i] === word[i] ||
word[i] === '.') {
i++;
} else {
break;
}
}
if (i === w.length) {
return true;
}
}
return false;
}
}
2. Depth First Search(Trie) Method
Words are stored in a trie, and searches are done using depth-first search (DFS). Wildcards (.) are handled by recursively checking all possible matches at each position, improving search efficiency to O(n) time complexity, where n is the word length.
- Time complexity: O(n) for addWord(), O(n) for search().
- Space complexity: O(t+n)
where n is the length of the string and t is the total number of TrieNodes created in the Trie.
Code
class TrieNode {
public:
vector<TrieNode*> children;
bool word;
TrieNode() : children(26, nullptr), word(false) {}
};
class WordDictionary {
public:
TrieNode* root;
WordDictionary() : root(new TrieNode()) {}
void addWord(string word) {
TrieNode* cur = root;
for (char c : word) {
if (cur->children[c - 'a'] == nullptr) {
cur->children[c - 'a'] = new TrieNode();
}
cur = cur->children[c - 'a'];
}
cur->word = true;
}
bool search(string word) {
return dfs(word, 0, root);
}
private:
bool dfs(string word, int j, TrieNode* root) {
TrieNode* cur = root;
for (int i = j; i < word.size(); i++) { char c = word[i]; if (c == '.') { for (TrieNode* child : cur->children) {
if (child != nullptr && dfs(word, i + 1, child)) {
return true;
}
}
return false;
} else {
if (cur->children[c - 'a'] == nullptr) {
return false;
}
cur = cur->children[c - 'a'];
}
}
return cur->word;
}
};
public class TrieNode {
TrieNode[] children;
boolean word;
public TrieNode() {
children = new TrieNode[26];
word = false;
}
}
public class WordDictionary {
private TrieNode root;
public WordDictionary() {
root = new TrieNode();
}
public void addWord(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
if (cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.word = true;
}
public boolean search(String word) {
return dfs(word, 0, root);
}
private boolean dfs(String word, int j, TrieNode root) {
TrieNode cur = root;
for (int i = j; i < word.length(); i++) {
char c = word.charAt(i);
if (c == '.') {
for (TrieNode child : cur.children) {
if (child != null && dfs(word, i + 1, child)) {
return true;
}
}
return false;
} else {
if (cur.children[c - 'a'] == null) {
return false;
}
cur = cur.children[c - 'a'];
}
}
return cur.word;
}
}
class TrieNode:
def __init__(self):
self.children = {}
self.word = False
class WordDictionary:
def __init__(self):
self.root = TrieNode()
def addWord(self, word: str) -> None:
cur = self.root
for c in word:
if c not in cur.children:
cur.children[c] = TrieNode()
cur = cur.children[c]
cur.word = True
def search(self, word: str) -> bool:
def dfs(j, root):
cur = root
for i in range(j, len(word)):
c = word[i]
if c == ".":
for child in cur.children.values():
if dfs(i + 1, child):
return True
return False
else:
if c not in cur.children:
return False
cur = cur.children[c]
return cur.word
return dfs(0, self.root)
class TrieNode {
constructor() {
this.children = Array(26).fill(null);
this.word = false;
}
}
class WordDictionary {
constructor() {
this.root = new TrieNode();
}
/**
* @param {string} c
* @return {number}
*/
getIndex(c) {
return c.charCodeAt(0) - 'a'.charCodeAt(0);
}
/**
* @param {string} word
* @return {void}
*/
addWord(word) {
let cur = this.root;
for (const c of word) {
const idx = this.getIndex(c);
if (cur.children[idx] === null) {
cur.children[idx] = new TrieNode();
}
cur = cur.children[idx];
}
cur.word = true;
}
/**
* @param {string} word
* @return {boolean}
*/
search(word) {
return this.dfs(word, 0, this.root);
}
/**
* @param {string} word
* @param {number} j
* @param {TrieNode} root
* @return {boolean}
*/
dfs(word, j, root) {
let cur = root;
for (let i = j; i < word.length; i++) {
const c = word[i];
if (c === '.') {
for (const child of cur.children) {
if (child !== null &&
this.dfs(word, i + 1, child)) {
return true;
}
}
return false;
} else {
const idx = this.getIndex(c);
if (cur.children[idx] === null) {
return false;
}
cur = cur.children[idx];
}
}
return cur.word;
}
}
