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) 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.

Examples

Example 1:

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

Solution

Method 1 - Use Hashmap Based Trie

This problem is similar with Implement Trie Problem. The solution 1 below uses the same definition of a trie node. To handle the “.” case for this problem, we need to search all possible paths, instead of one path.

Here is TrieNode from Map-based Trie Implementation:

class TrieNode {
	HashMap<Character, TrieNode> children = new HashMap<Character, TrieNode> ();
	boolean isLeaf;
}

Here is WordDictionary:

public class WordDictionary {
	private final TrieNode root = new TrieNode();  
	  
    // Adds a word into the data structure.
    public void addWord(String word) {
        TrieNode curr = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (!curr.children.containsKey(c)) {
                curr.children.put(c, new TrieNode());
            }
            curr = curr.children.get(c);
        }
        curr.isLeaf = true;
    }

    // Returns if the word is in the data structure. A word could
    // contain the dot character '.' to represent any one letter.
    public boolean search(String word) {
        return dfsSearch(word, root);
    }

    private boolean dfsSearch(String word, TrieNode node) {
        if (word == null || (word.isEmpty())) {
            return node.isLeaf;
        }
        char c = word.charAt(0);
        if (c == '.') {
            for (TrieNode child : node.children.values()) {
                if (dfsSearch(word.substring(1), child)) {
                    return true;
                }
            }
        }
        //if character is neither equal to the node nor '.', return false
        if (!node.children.containsKey(c)) {
            return false;
        }
        return dfsSearch(word.substring(1), node.children.get(c));
    }
}

Our search method is a bit heavy, as we are creating multiple substring of the word. here is a lighter method:

public boolean search(String word) {
	return dfsSearch(word, 0, root);
}

private boolean dfsSearch(String word, int pos, TrieNode node) {
	if (pos == word.length()) {
		return node.isWord;
	}
	
	char c = word.charAt(pos);
	if (c == '.') {
		for (TrieNode child : node.children.values()) {
			if (dfsSearch(word, pos+1, child)) {
				return true;
			}
		}
		return false;
	}
	
	//if character at position 'pos' is neither equal to the node nor '.', return false
	if (!node.children.containsKey(word.charAt(pos))) {
		return false;
	}

	return dfsSearch(word, pos + 1, node.children.get(c));
}

Method 2 - Using Array Based Trie Instead of HashMap

We can also use Array-based Trie Implementation.

class TrieNode {
	TrieNode[] children;
	boolean isLeaf;

	public TrieNode() {
		children = new TrieNode[26];
	}
}

public class WordDictionary {
	private final TrieNode root;

	public WordDictionary() {
		root = new TrieNode();
	}

	// Adds a word into the data structure.  
	public void addWord(String word) {
		TrieNode p = root;
		for (int i = 0; i<word.length(); i++) {
			char c = word.charAt(i);
			int index = c - 'a';
			if (p.children[index] == null) {
				TrieNode temp = new TrieNode();
				p.children[index] = temp;
			}
			p = p.children[index];
		}

		p.isLeaf = true;
	}

	// Returns if the word is in the data structure. A word could  
	// contain the dot character '.' to represent any one letter.  
	public boolean search(String word) {
		return dfsSearch(word, 0, root);
	}

	public boolean dfsSearch(String word, int pos, TrieNode p) {
		if (p.isLeaf && pos == word.length())
			return true;

		if (pos >= word.length())
			return false;

		char c = word.charAt(pos);

		if (c == '.') {
			for (TrieNode child: p.children) {
				if (child != null) {
					if (dfsSearch(word, pos + 1, child)) {
						return true;
					}
				}
			}
			return false;

		}
		int index = c - 'a';

		if (p.children[index] == null) {
			return false;
		}

		return dfsSearch(word, pos + 1, p.children[index]);
	}
}