THN Interview Prep

211. Design Add and Search Words Data Structure

At a Glance

Problem (one-liner)

Design a dictionary with addWord(word) and searchWord(pattern) where pattern may contain lowercase letters or . matching any single character.

Recognition Cues

  • Wildcard search over stored vocabulary → trie branches out on .
  • Backtracking / DFS from trie node when hitting wildcard
  • Exact match still linear along edges when letters fixed

Diagram

At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.

Loading diagram…

Approaches

  • List + regex — slow rebuild / compile per query.
  • Trie + DFSO(26^dots * L) worst for dotted patterns but matches interview expectation.

Optimal Solution

Go

package main

type WordDictionary struct {
	children [26]*WordDictionary
	isWord   bool
}

func Constructor() WordDictionary {
	return WordDictionary{}
}

func (dict *WordDictionary) AddWord(word string) {
	node := dict
	for index := range word {
		offset := int(word[index] - 'a')
		if node.children[offset] == nil {
			node.children[offset] = &WordDictionary{}
		}
		node = node.children[offset]
	}
	node.isWord = true
}

func (dict *WordDictionary) Search(word string) bool {
	var match func(int, *WordDictionary) bool
	match = func(position int, node *WordDictionary) bool {
		if node == nil {
			return false
		}
		if position == len(word) {
			return node.isWord
		}
		letter := word[position]
		if letter == '.' {
			for offset := range node.children {
				child := node.children[offset]
				if child != nil && match(position+1, child) {
					return true
				}
			}
			return false
		}
		offset := int(letter - 'a')
		return match(position+1, node.children[offset])
	}
	return match(0, dict)
}

JavaScript

class WordDictionary {
	constructor() {
		this.children = new Map();
		this.isWord = false;
	}

	addWord(word) {
		let node = this;
		for (let index = 0; index < word.length; index++) {
			const letter = word[index];
			if (!node.children.has(letter)) {
				node.children.set(letter, new WordDictionary());
			}
			node = node.children.get(letter);
		}
		node.isWord = true;
	}

	search(word) {
		const match = (position, node) => {
			if (node === undefined || node === null) {
				return false;
			}
			if (position === word.length) {
				return node.isWord;
			}
			const letter = word[position];
			if (letter === '.') {
				for (const child of node.children.values()) {
					if (match(position + 1, child)) {
						return true;
					}
				}
				return false;
			}
			return match(position + 1, node.children.get(letter));
		};
		return match(0, this);
	}
}

Walkthrough

Add "bad", "dad". Search "d.d":

positionletteraction
0ddescend to d child
1.try every child under current node
2dconfirm terminal word exists

Invariant: match explores all alphabet branches exactly when wildcard consumes a slot; otherwise deterministic descent.

Edge Cases

  • Pattern longer than any stored word → false
  • Multiple dots → exponential branching in worst case

Pitfalls

  • Treating . like empty string (it consumes one character slot)
  • Not checking isWord only at full pattern length

Similar Problems

Variants

  • Multi-character wildcards or * Kleene (harder; often backtracking on NFA).
  • Count matches instead of boolean search.

Mind-Map Tags

#trie #wildcard #dfs #backtracking #dictionary-design

Last updated on

Spotted something unclear or wrong on this page?

On this page