Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Move from brute-force thinking to an efficient approach using design strategy.
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) Adds word to the data structure, it can be matched later.bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots '.' where dots can be matched with any letter.Example:
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 <= 25word in addWord consists of lowercase English letters.word in search consist of '.' or lowercase English letters.2 dots in word for search queries.104 calls will be made to addWord and search.Problem summary: 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) Adds word to the data structure, it can be matched later. bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots '.' where dots can be matched with any letter.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Design · Trie
["WordDictionary","addWord","addWord","addWord","search","search","search","search"] [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
implement-trie-prefix-tree)prefix-and-suffix-search)match-substring-after-replacement)sum-of-prefix-scores-of-strings)count-prefix-and-suffix-pairs-ii)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #211: Design Add and Search Words Data Structure
class Trie {
Trie[] children = new Trie[26];
boolean isEnd;
}
class WordDictionary {
private Trie trie;
/** Initialize your data structure here. */
public WordDictionary() {
trie = new Trie();
}
public void addWord(String word) {
Trie node = trie;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) {
node.children[idx] = new Trie();
}
node = node.children[idx];
}
node.isEnd = true;
}
public boolean search(String word) {
return search(word, trie);
}
private boolean search(String word, Trie node) {
for (int i = 0; i < word.length(); ++i) {
char c = word.charAt(i);
int idx = c - 'a';
if (c != '.' && node.children[idx] == null) {
return false;
}
if (c == '.') {
for (Trie child : node.children) {
if (child != null && search(word.substring(i + 1), child)) {
return true;
}
}
return false;
}
node = node.children[idx];
}
return node.isEnd;
}
}
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* boolean param_2 = obj.search(word);
*/
// Accepted solution for LeetCode #211: Design Add and Search Words Data Structure
type WordDictionary struct {
root *trie
}
func Constructor() WordDictionary {
return WordDictionary{new(trie)}
}
func (this *WordDictionary) AddWord(word string) {
this.root.insert(word)
}
func (this *WordDictionary) Search(word string) bool {
n := len(word)
var dfs func(int, *trie) bool
dfs = func(i int, cur *trie) bool {
if i == n {
return cur.isEnd
}
c := word[i]
if c != '.' {
child := cur.children[c-'a']
if child != nil && dfs(i+1, child) {
return true
}
} else {
for _, child := range cur.children {
if child != nil && dfs(i+1, child) {
return true
}
}
}
return false
}
return dfs(0, this.root)
}
type trie struct {
children [26]*trie
isEnd bool
}
func (t *trie) insert(word string) {
cur := t
for _, c := range word {
c -= 'a'
if cur.children[c] == nil {
cur.children[c] = new(trie)
}
cur = cur.children[c]
}
cur.isEnd = true
}
/**
* Your WordDictionary object will be instantiated and called as such:
* obj := Constructor();
* obj.AddWord(word);
* param_2 := obj.Search(word);
*/
# Accepted solution for LeetCode #211: Design Add and Search Words Data Structure
class Trie:
def __init__(self):
self.children = [None] * 26
self.is_end = False
class WordDictionary:
def __init__(self):
self.trie = Trie()
def addWord(self, word: str) -> None:
node = self.trie
for c in word:
idx = ord(c) - ord('a')
if node.children[idx] is None:
node.children[idx] = Trie()
node = node.children[idx]
node.is_end = True
def search(self, word: str) -> bool:
def search(word, node):
for i in range(len(word)):
c = word[i]
idx = ord(c) - ord('a')
if c != '.' and node.children[idx] is None:
return False
if c == '.':
for child in node.children:
if child is not None and search(word[i + 1 :], child):
return True
return False
node = node.children[idx]
return node.is_end
return search(word, self.trie)
# Your WordDictionary object will be instantiated and called as such:
# obj = WordDictionary()
# obj.addWord(word)
# param_2 = obj.search(word)
// Accepted solution for LeetCode #211: Design Add and Search Words Data Structure
#[derive(Default)]
struct WordDictionary {
is_word_end: bool,
children: [Option<Box<WordDictionary>>; 26],
}
impl WordDictionary {
fn new() -> Self {
Default::default()
}
fn add_word(&mut self, word: String) {
let mut dict = self;
for c in word.chars(){
dict = dict.children[char_index(c)]
.get_or_insert(Box::new(WordDictionary::new()))
.as_mut();
}
dict.is_word_end = true;
}
fn search_ref(&self, word: &str) -> bool{
let mut dict = self;
for (i, c) in word.chars().enumerate(){
if c == '.'{
let res = dict.children
.iter()
.any(|opt|{
if let Some(ref next_dict) = opt{
next_dict.search_ref(&word[i + 1..])
}else{
false
}
});
return res;
}else{
if let Some(ref next_dict) = dict.children[char_index(c)]{
dict = next_dict.as_ref();
}else{
return false;
}
}
}
dict.is_word_end
}
fn search(&self, word: String) -> bool {
self.search_ref(&word)
}
}
pub fn char_index(c: char) -> usize{
c as usize - 'a' as usize
}
// Accepted solution for LeetCode #211: Design Add and Search Words Data Structure
class TrieNode {
children: { [key: string]: TrieNode };
endOfWord: boolean;
constructor() {
this.children = {};
this.endOfWord = false;
}
}
class WordDictionary {
root: TrieNode;
constructor() {
this.root = new TrieNode();
}
addWord(word: string): void {
let cur = this.root;
for (const c of word) {
if (!(c in cur.children)) {
cur.children[c] = new TrieNode();
}
cur = cur.children[c];
}
cur.endOfWord = true;
}
search(word: string): boolean {
function dfs(j: number, root: TrieNode): boolean {
let cur = root;
for (let i = j; i < word.length; i++) {
const c = word[i];
if (c === '.') {
for (const key in cur.children) {
if (
Object.prototype.hasOwnProperty.call(
cur.children,
key,
)
) {
const child = cur.children[key];
if (dfs(i + 1, child)) {
return true;
}
}
}
return false;
} else {
if (!(c in cur.children)) {
return false;
}
cur = cur.children[c];
}
}
return cur.endOfWord;
}
return dfs(0, this.root);
}
}
Use this to step through a reusable interview workflow for this problem.
Use a simple list or array for storage. Each operation (get, put, remove) requires a linear scan to find the target element — O(n) per operation. Space is O(n) to store the data. The linear search makes this impractical for frequent operations.
Design problems target O(1) amortized per operation by combining data structures (hash map + doubly-linked list for LRU, stack + min-tracking for MinStack). Space is always at least O(n) to store the data. The challenge is achieving constant-time operations through clever structure composition.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.