package com.freetymekiyan.algorithms.level.medium; import org.junit.Assert; import org.junit.Test; /** * Given a 2D board and a word, find if the word exists in the grid. * <p> * The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally * or vertically neighboring. The same letter cell may not be used more than once. * <p> * For example, * Given board = * <p> * | [ * | ['A','B','C','E'], * | ['S','F','C','S'], * | ['A','D','E','E'] * | ] * word = "ABCCED", -> returns true, * word = "SEE", -> returns true, * word = "ABCB", -> returns false. * <p> * Company Tags: Microsoft, Bloomberg, Facebook * Tags: Array, Backtracking * Similar Problems: (H) Word Search II */ public class WordSearch { /** * Backtracking. * For each character in board, start backtracking if the first character matches. */ public boolean exist(char[][] board, String word) { if (board == null || board.length == 0 || board[0].length == 0 || word == null) { return false; } if (word.length() == 0) { return true; } for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (board[i][j] == word.charAt(0)) { // Match the first character. if (backtrack(board, i, j, word, 0)) { return true; } } } } return false; } /** * Backtracking. * Statement: * Given board, starting position, word, and current position in word. * Find whether the word is in board. * Recursion: * Check current character. If diff, return false. If same, recurse 4 neighbors with subset. * Base case: * If pos is at word's end, return true. * If i, j is not within the board, return false. * If characters are different, return false. * Implementation: * Deal with base cases. * Mark current position with '#' as visited. * Recurse the 4 adjacent grids. * Reset the mark. * Return true if one of the adjacent grid is true. */ public boolean backtrack(char[][] board, int i, int j, String word, int pos) { if (word.length() == pos) { return true; } // Out of board or doesn't match. if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != word.charAt(pos)) { return false; } board[i][j] = '#'; // Mark as visited. // Search 4 neighbors. boolean res = backtrack(board, i - 1, j, word, pos + 1) || backtrack(board, i + 1, j, word, pos + 1) || backtrack( board, i, j - 1, word, pos + 1) || backtrack(board, i, j + 1, word, pos + 1); board[i][j] = word.charAt(pos);// Reset. return res; } public boolean existB(char[][] board, String word) { for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[i].length; j++) { if (board[i][j] == word.charAt(0)) { if (backtrack(board, word, 0, i, j)) { return true; } } } } return false; } /** * Backtracking. * Optimized in terms of: * 1) Don't search out of board coordinates; * 2) Don't search already visited grid; * 3) If already found one solution, skip the rest. */ private boolean backtrack(char[][] board, String word, int pos, int i, int j) { if (word.charAt(pos) == board[i][j] && pos == word.length() - 1) { return true; } if (word.charAt(pos) != board[i][j]) { return false; } board[i][j] = '#'; // Mark visited. boolean res = false; if (!res && i + 1 < board.length && board[i + 1][j] != '#') { res = res || backtrack(board, word, pos + 1, i + 1, j); } if (!res && i - 1 >= 0 && board[i - 1][j] != '#') { res = res || backtrack(board, word, pos + 1, i - 1, j); } if (!res && j + 1 < board[i].length && board[i][j + 1] != '#') { res = res || backtrack(board, word, pos + 1, i, j + 1); } if (!res && j - 1 >= 0 && board[i][j - 1] != '#') { res = res || backtrack(board, word, pos + 1, i, j - 1); } board[i][j] = word.charAt(pos); // Reset. return res; } @Test public void testExamples() { char[][] board = { {'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'} }; String word = "ABCCED"; Assert.assertTrue(exist(board, word)); Assert.assertTrue(existB(board, word)); word = "SEE"; Assert.assertTrue(exist(board, word)); Assert.assertTrue(existB(board, word)); word = "ABCB"; Assert.assertFalse(exist(board, word)); Assert.assertFalse(existB(board, word)); } }