Brute Force Approach : Time Complexity will be O(num. of words * M * 4 * 3^L-1)
- M = number of cells in the 2D matrix
- L = length ofmaximum length of
words.
public class WordSearchII {
int flag = 0;
public List<String> findWords(char[][] b, String[] words) {
List<String> result = new ArrayList<>();
for (int k = 0; k < words.length; k++) {
flag = 0;
/*
* Find word's first letter. Then call method to check it's surroundings
*/
for (int r = 0; r < b.length; r++) {
for (int c = 0; c < b[0].length; c++) {
if (b[r][c] == words[k].charAt(0) && dfs(b, words[k], 0, r, c)) {
if (flag == 1) {
result.add(words[k]);
break;
}
}
}
if (flag == 1) {
break;
}
}
}
return result;
// return new ArrayList<>(new HashSet<>(result));
}
public boolean dfs(char[][] b, String word, int start, int r, int c) {
/* once we get past word.length, we are done. */
if (word.length() <= start) {
flag = 1;
return true;
}
/*
* if off bounds, letter is seen, letter is unequal to word.charAt(start)
* return false
*/
if (r < 0 || c < 0 || r >= b.length || c >= b[0].length || b[r][c] == '0' || b[r][c] != word.charAt(start))
return false;
/* set this board position to seen. (Because we can use this postion) */
char tmp = b[r][c];
b[r][c] = '0';
/* recursion on all 4 sides for next letter, if works: return true */
if (dfs(b, word, start + 1, r + 1, c) || dfs(b, word, start + 1, r - 1, c) || dfs(b, word, start + 1, r, c + 1)
|| dfs(b, word, start + 1, r, c - 1)) {
// Set back to unseen
b[r][c] = tmp;
return true;
}
// Set back to unseen
b[r][c] = tmp;
return false;
}
}
Trie-based approach
- Time Complexity reduces to O(M * 4 * 3^L-1)
- Introduces need for space O(2N); in case of worst case when Trie would have as many nodes as the letters of all words, where N is the total number of letters. Because we also store strings to be searched N becomes 2N
public class WordSearchIIWithTwist {
char[][] _board = null;
ArrayList<String> _result = new ArrayList<String>();
TrieNode root = new TrieNode();
public List<String> findWords(char[][] board, String[] words) {
// Step 1). Construct the Trie
for (int i = 0; i < words.length; i++) {
char[] arr = words[i].toCharArray();
TrieNode current = root;
for (int j = 0; j < arr.length; j++) {
if (!current.children.containsKey(arr[j])) {
current.children.put(arr[j], new TrieNode());
}
current = current.children.get(arr[j]);
}
current.word = words[i];
}
this._board = board;
// Step 2). Backtracking starting for each cell in the board
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (root.children.containsKey(board[i][j])) {
dfs(i, j, root);
}
}
}
return _result;
}
private void dfs(int row, int col, TrieNode parent) {
if (row < 0 || col < 0 || row >= _board.length || col >= _board[0].length || _board[row][col] == '#') {
return;
}
char letter = this._board[row][col];
if (!parent.children.containsKey(letter)) {
return;
}
TrieNode nextNode = parent.children.get(letter);
// check if there is any match
if (nextNode.word != null) {
_result.add(nextNode.word);
nextNode.word = null;
}
// mark the current letter before the EXPLORATION
this._board[row][col] = '#';
// explore neighbor cells in 4 directions: up, down, left, right
dfs(row + 1, col, nextNode);
dfs(row - 1, col, nextNode);
dfs(row, col - 1, nextNode);
dfs(row, col + 1, nextNode);
this._board[row][col] = letter;
}
public static void main(String[] args) {
WordSearchIIWithTwist a = new WordSearchIIWithTwist();
System.out.println(a.findWords(new char[][] { { 'a' } }, new String[] { "a" }));
}
}
class TrieNode {
Map<Character, TrieNode> children = new HashMap<>();
String word = null;
public TrieNode() {
};
}