Solution: Word Search

Let's solve the Word Search problem using the Backtracking pattern.

Statement

Given an m×nm \times n 2D grid of characters and word as a string, we need to determine if the word can be constructed from letters of sequentially adjacent cells. The cells are considered sequentially adjacent when they are neighbors to each other either horizontally or vertically. The function should return TRUE if the word can be constructed and FALSE otherwise.

Constraints:

  • m == board.length
  • n == board[i].length, where 00 \leq i << m
  • 11 \leq m, n 6\leq 6
  • 11 \leq word.length 15\leq 15
  • board and word consist of only lowercase or uppercase English letters.
  • The search is not case-sensitive.

Pattern: Backtracking

In order to search for a word in a 2-D grid, we need to traverse all the elements of the grid. The sequence of letters that combine to make a word can be found starting from any index in the grid. Therefore, we need to find that word in all possible directions, starting from a particular index. However, what would we do if we learned that the current path would not lead to the solution?

The answer to the problem above lies in backtracking. We can move backward from the current index and resume the search for the required word along one of the other possible paths.

Solution

This problem can be solved efficiently by implementing the backtracking technique. This will help us traverse all the possibilities to find the specific word without using the extra space needed to keep track of the visited or non-visited status of the cells.

Note: In the following section, we’ll gradually build the solution. Alternatively, you can skip straight to just the code.

Step-by-step solution construction

For each cell on the 2D grid, we have four paths we can take next. If the chosen path is incorrect, we’ll backtrack and choose a different path until the word is found or all possibilities are exhausted. We can implement the DFS algorithm using a recursive function.

Note: Although the DFS algorithm is classically implemented on a tree or graph data structure, it may also be applied wherever the concept of connectedness is found in a data structure. In the 2D array provided as input, we see that each character located at cell {i,ji, j} is connected to its four potential neighbors, each addressable as cells {i+1,ji+1, j} (the character below), {i1,ji-1, j} (the character above), {i,j+1i, j+1} (the character to the right), and {i,j1i, j-1} (the character to the left). In graph terminology, we can say that each cell in the input grid has a minimum of two and a maximum of four edges.

We’ll apply a depth-first search for each cell of the grid. We’ll check for the base case in the depthFirstSearch function. The base case occurs when we have matched all characters in the word individually, indicating that we have found the whole word in the grid. If this is the case, we have no more letters to explore in the word, so the length of the word becomes 00. Therefore, we return TRUE.

import java.util.*;
class WordSearch {
//# Function to search a specific word in the grid
public static boolean wordSearch(char[][] grid, String word){
int n = grid.length;
int m = grid[0].length;
for(int row = 0; row < grid.length; row++){
for(int col = 0; col < grid[0].length; col++){
if(depthFirstSearch(row, col, word, 0, grid)){
return true;
}
}
}
return false;
}
// Apply backtracking on every element to search the required word
public static boolean depthFirstSearch(int row, int col, String word, int index, char[][] grid){
// base case
if (word.length() == index){
return true;
}
return false;
}
public static void printGrid(char[][] grid) {
for (int i = 0; i < grid.length; i++) {
System.out.print("\t\t[");
for (int j = 0; j < grid[0].length; j++) {
if (j < grid[0].length - 1)
System.out.print("'" + grid[i][j] + "', ");
else
System.out.print("'" + grid[i][j] + "'");
}
System.out.println("]");
}
System.out.println("\n");
}
public static void main( String args[] ) {
char[][][] grids = {
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'U', 'F', 'M', 'Q'},
{'I', 'C', 'Q', 'R', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'A', 'F', 'M', 'Q'},
{'I', 'C', 'A', 'S', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'h', 'e', 'c', 'm', 'l'},
{'w', 'l', 'i', 'e', 'u'},
{'a', 'r', 'r', 's', 'n'},
{'s', 'i', 'i', 'o', 'r'}},
{{'C', 'Q', 'N', 'A'},
{'P', 'S', 'E', 'I'},
{'Z', 'A', 'P', 'E'},
{'J', 'V', 'T', 'K'}},
{{'O', 'Y', 'O', 'I'},
{'B', 'Y', 'N', 'M'},
{'K', 'D', 'A', 'R'},
{'C', 'I', 'M', 'I'},
{'Z', 'I', 'T', 'O'}}
};
String[] words = {"EDUCATIVE", "PACANS", "warrior", "SAVE", "DYNAMIC"};
for(int i = 0; i < words.length; i++){
System.out.println((i+1) + ".\tGrid = ");
printGrid(grids[i]);
System.out.println("\tWord = "+ words[i]);
System.out.println("\n\tProcessing...");
Boolean result = wordSearch(grids[i], words[i]);
if(result == true){
System.out.println("\n\tSearch result = Found Word");
}
else{
System.out.println("\n\tSearch result = Word could not be found");
}
System.out.println(new String(new char[100]).replace('\0', '-'));
}
}
}
Word Search

After the base case, we need to check if the current cell is a valid candidate for the next character of the word. In line 25, we do this by checking if the row and column indices are within the bounds of the grid and we also check if the character in the cell matches the next character in the word. If any of these conditions are not satisfied, we cannot proceed with the current cell, and we return FALSE to backtrack and explore other paths.

import java.util.*;
class WordSearch {
// Function to search a specific word in the grid
public static boolean wordSearch(char[][] grid, String word){
int n = grid.length;
int m = grid[0].length;
for(int row = 0; row < grid.length; row++){
for(int col = 0; col < grid[0].length; col++){
if(depthFirstSearch(row, col, word, 0, grid)){
return true;
}
}
}
return false;
}
//Apply backtracking on every element to search the required word
public static boolean depthFirstSearch(int row, int col, String word, int index, char[][] grid){
// base case
if (word.length() == index){
return true;
}
// Check if the cell is not out of bounds or particular grid
// element is not among required characters
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length || (grid[row][col] != word.charAt(index))){
return false;
}
return false;
}
public static void printGrid(char[][] grid) {
for (int i = 0; i < grid.length; i++) {
System.out.print("\t\t[");
for (int j = 0; j < grid[0].length; j++) {
if (j < grid[0].length - 1)
System.out.print("'" + grid[i][j] + "', ");
else
System.out.print("'" + grid[i][j] + "'");
}
System.out.println("]");
}
System.out.println("\n");
}
public static void main( String args[] ) {
char[][][] grids = {
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'U', 'F', 'M', 'Q'},
{'I', 'C', 'Q', 'R', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'A', 'F', 'M', 'Q'},
{'I', 'C', 'A', 'S', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'h', 'e', 'c', 'm', 'l'},
{'w', 'l', 'i', 'e', 'u'},
{'a', 'r', 'r', 's', 'n'},
{'s', 'i', 'i', 'o', 'r'}},
{{'C', 'Q', 'N', 'A'},
{'P', 'S', 'E', 'I'},
{'Z', 'A', 'P', 'E'},
{'J', 'V', 'T', 'K'}},
{{'O', 'Y', 'O', 'I'},
{'B', 'Y', 'N', 'M'},
{'K', 'D', 'A', 'R'},
{'C', 'I', 'M', 'I'},
{'Z', 'I', 'T', 'O'}}
};
String[] words = {"EDUCATIVE", "PACANS", "warrior", "SAVE", "DYNAMIC"};
for(int i=0;i<words.length;i++){
System.out.print(i+1);
System.out.println(".\tGrid: ");
printGrid(grids[i]);
System.out.println("\tWord = "+ words[i]);
System.out.println("\n\tProcessing...");
Boolean result = wordSearch(grids[i], words[i]);
if(result == true){
System.out.println("\n\tSearch result = Found Word");
}
else{
System.out.println("\n\tSearch result = Word could not be found");
}
System.out.println(new String(new char[100]).replace('\0', '-'));
}
}
}
Word Search

After ensuring that the current cell is not out of bounds and contains the next character of the word, we mark the cell as visited by setting it to *, and store its original value in a temporary variable, temp. Then, we iterate over the four possible choices (up, down, right, left) and call the depthFirstSearch function recursively with the updated row and column indices and the remaining portion of the word.

For each possible choice, we check if the recursive call to depthFirstSearch returns TRUE. If the call returns TRUE, indicating that we have found the whole word, we return TRUE. If not, we will continue the loop and try the next possible choice. If none of the choices lead to finding the whole word, we will return FALSE.

import java.util.*;
class WordSearch {
// Function to search a specific word in the grid
public static boolean wordSearch(char[][] grid, String word){
int n = grid.length;
int m = grid[0].length;
for(int row = 0; row < grid.length; row++){
for(int col = 0; col < grid[0].length; col++){
if(depthFirstSearch(row, col, word, 0, grid)){
return true;
}
}
}
return false;
}
// Apply backtracking on every element to search the required word
public static boolean depthFirstSearch(int row, int col, String word, int index, char[][] grid) {
// base case
if (word.length() == index) {
return true;
}
// Check if the cell is not out of bounds or particular grid
// element is not among required characters
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length || (grid[row][col] != word.charAt(index))) {
System.out.println("\tDid not find " + word.charAt(index) + " at cell [" + row + ", " + col + "]");
return false;
}
boolean result = false;
// Mark the cell as visited
System.out.println("\n\tFound " + grid[row][col] + " at cell [" + row + ", " + col + "]");
if (word.length() > 1) {
System.out.println("\tGoing to check its neighbors:");
printGridNeighbors(row, col, grid, "\t\t");
} else {
System.out.println("\tWord found!");
}
// Store original value of the grid in temp
char temp = grid[row][col];
// mark the cell as visited
grid[row][col] = '*';
// Explore the four neighboring directions
// that is right, left, up, down by adding
// (0, 1), (1, 0), (0, -1), (-1, 0) indices respectively
int[][] offsets = {
{0, 1}, {1, 0}, {0, -1}, {-1, 0}
};
for (int[] offset: offsets) {
int rowOffset = offset[0];
int colOffset = offset[1];
result = depthFirstSearch(row + rowOffset, col + colOffset, word, index + 1, grid);
// Break instead of implementing all steps and wasting our time
if (result) {
break;
}
}
if (!result)
System.out.println("\tNone of the neighbors matched. Backtracking...");
return result;
}
static void printGridNeighbors(int row, int col, char[][] grid, String offset) {
if (row > 0) {
if (col == 0) {
System.out.println(offset + '|');
} else {
System.out.println(offset + "_|" + grid[row - 1][col] + "|_");
}
}
if (col > 0) {
System.out.print(offset + grid[row][col - 1] + "{" + grid[row][col] + "}");
} else {
System.out.print(offset + "{" + grid[row][col] + "}");
}
if (col < (grid[0].length - 1)) {
System.out.print(grid[row][col + 1]);
}
System.out.print("\n");
if (row < (grid.length - 1)) {
if (col == 0)
System.out.println(offset + '|' + grid[row + 1][col] + "|‾");
else
System.out.println(offset + "‾|" + grid[row + 1][col] + "|‾");
}
}
public static void printGrid(char[][] grid) {
for (int i = 0; i < grid.length; i++) {
System.out.print("\t\t[");
for (int j = 0; j < grid[0].length; j++) {
if (j < grid[0].length - 1)
System.out.print("'" + grid[i][j] + "', ");
else
System.out.print("'" + grid[i][j] + "'");
}
System.out.println("]");
}
System.out.println("\n");
}
public static void main( String args[] ) {
char[][][] grids = {
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'U', 'F', 'M', 'Q'},
{'I', 'C', 'Q', 'R', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'A', 'F', 'M', 'Q'},
{'I', 'C', 'A', 'S', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'h', 'e', 'c', 'm', 'l'},
{'w', 'l', 'i', 'e', 'u'},
{'a', 'r', 'r', 's', 'n'},
{'s', 'i', 'i', 'o', 'r'}},
{{'C', 'Q', 'N', 'A'},
{'P', 'S', 'E', 'I'},
{'Z', 'A', 'P', 'E'},
{'J', 'V', 'T', 'K'}},
{{'O', 'Y', 'O', 'I'},
{'B', 'Y', 'N', 'M'},
{'K', 'D', 'A', 'R'},
{'C', 'I', 'M', 'I'},
{'Z', 'I', 'T', 'O'}}
};
String[] words = {"EDUCATIVE", "PACANS", "warrior", "SAVE", "DYNAMIC"};
for(int i = 0; i < words.length; i++){
System.out.println((i+1) + ".\tGrid = ");
printGrid(grids[i]);
System.out.println("\tWord = "+ words[i]);
System.out.println("\n\tProcessing...");
Boolean result = wordSearch(grids[i], words[i]);
if(result == true){
System.out.println("\n\tSearch result = Found Word");
}
else{
System.out.println("\n\tSearch result = Word could not be found");
}
System.out.println(new String(new char[100]).replace('\0', '-'));
}
}
}
Word Search

When the function has finished exploring all possible paths from the current cell and returns FALSE, we’ll revert the cell’s original value, stored in temp, to unmark it and allow it to be visited in other paths. This is important because, if we don’t reset the cell’s value, it would still be marked as visited, and the search would be incomplete or miss out on other possible paths.

import java.util.*;
class WordSearch {
// Function to search a specific word in the grid
public static boolean wordSearch(char[][] grid, String word){
int n = grid.length;
int m = grid[0].length;
for(int row = 0; row < grid.length; row++){
for(int col = 0; col < grid[0].length; col++){
if(depthFirstSearch(row, col, word, 0, grid)){
return true;
}
}
}
return false;
}
//Apply backtracking on every element to search the required word
public static boolean depthFirstSearch(int row, int col, String word, int index, char[][] grid) {
// base case
if (word.length() == index) {
return true;
}
// Check if the cell is not out of bounds or particular grid
// element is not among required characters
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length || (grid[row][col] != word.charAt(index))) {
System.out.println("\tDid not find " + word.charAt(index) + " at cell [" + row + ", " + col + "]");
return false;
}
boolean result = false;
// Mark the cell as visited
System.out.println("\n\tFound " + grid[row][col] + " at cell [" + row + ", " + col + "]");
if (word.length() > 1) {
System.out.println("\tGoing to check its neighbors:");
printGridNeighbors(row, col, grid, "\t\t");
} else {
System.out.println("\tWord found!");
}
char temp = grid[row][col];
grid[row][col] = '*';
// Explore the four neighboring directions
// that is right, left, up, down by adding
// (0, 1), (1, 0), (0, -1), (-1, 0) indices respectively
int[][] offsets = {
{0, 1}, {1, 0}, {0, -1}, {-1, 0}
};
for (int[] offset: offsets) {
int rowOffset = offset[0];
int colOffset = offset[1];
result = depthFirstSearch(row + rowOffset, col + colOffset, word, index + 1, grid);
// Break instead of implementing all steps and wasting our time
if (result) {
break;
}
}
if(!result)
System.out.println("\tNone of the neighbors matched. Backtracking...");
// this will revert back the original value of the cell
grid[row][col] = temp;
return result;
}
static void printGridNeighbors(int row, int col, char[][] grid, String offset) {
if (row > 0) {
if (col == 0) {
System.out.println(offset + '|');
} else {
System.out.println(offset + "_|" + grid[row - 1][col] + "|_");
}
}
if (col > 0) {
System.out.print(offset + grid[row][col - 1] + "{" + grid[row][col] + "}");
} else {
System.out.print(offset + "{" + grid[row][col] + "}");
}
if (col < (grid[0].length - 1)) {
System.out.print(grid[row][col + 1]);
}
System.out.print("\n");
if (row < (grid.length - 1)) {
if (col == 0)
System.out.println(offset + '|' + grid[row + 1][col] + "|‾");
else
System.out.println(offset + "‾|" + grid[row + 1][col] + "|‾");
}
}
public static void printGrid(char[][] grid) {
for (int i = 0; i < grid.length; i++) {
System.out.print("\t\t[");
for (int j = 0; j < grid[0].length; j++) {
if (j < grid[0].length - 1)
System.out.print("'" + grid[i][j] + "', ");
else
System.out.print("'" + grid[i][j] + "'");
}
System.out.println("]");
}
System.out.println("\n");
}
public static void main( String args[] ) {
char[][][] grids = {
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'U', 'F', 'M', 'Q'},
{'I', 'C', 'Q', 'R', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'A', 'F', 'M', 'Q'},
{'I', 'C', 'A', 'S', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'h', 'e', 'c', 'm', 'l'},
{'w', 'l', 'i', 'e', 'u'},
{'a', 'r', 'r', 's', 'n'},
{'s', 'i', 'i', 'o', 'r'}},
{{'C', 'Q', 'N', 'A'},
{'P', 'S', 'E', 'I'},
{'Z', 'A', 'P', 'E'},
{'J', 'V', 'T', 'K'}},
{{'O', 'Y', 'O', 'I'},
{'B', 'Y', 'N', 'M'},
{'K', 'D', 'A', 'R'},
{'C', 'I', 'M', 'I'},
{'Z', 'I', 'T', 'O'}}
};
String[] words = {"EDUCATIVE", "PACANS", "warrior", "SAVE", "DYNAMIC"};
for(int i=0;i<words.length;i++){
System.out.print(i+1);
System.out.println(".\tGrid = ");
printGrid(grids[i]);
System.out.println("\tWord = "+ words[i]);
System.out.println("\n\tProcessing...");
Boolean result = wordSearch(grids[i], words[i]);
if(result == true){
System.out.println("\n\tSearch result = Found Word");
}
else{
System.out.println("\n\tSearch result = Word could not be found");
}
System.out.println(new String(new char[100]).replace('\0', '-'));
}
}
}
Word Search

Just the code

Here’s the complete solution to this problem:

import java.util.*;
class WordSearch {
// Function to search a specific word in the grid
public static boolean wordSearch(char[][] grid, String word){
int n = grid.length;
int m = grid[0].length;
for(int row = 0; row < grid.length; row++){
for(int col = 0; col < grid[0].length; col++){
if(depthFirstSearch(row, col, word, 0, grid)){
return true;
}
}
}
return false;
}
//Apply backtracking on every element to search the required word
public static boolean depthFirstSearch(int row, int col, String word, int index, char[][] grid) {
if (word.length() == index) {
return true;
}
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length || (grid[row][col] != word.charAt(index))) {
return false;
}
boolean result = false;
char temp = grid[row][col];
grid[row][col] = '*';
int[][] offsets = {
{0, 1}, {1, 0}, {0, -1}, {-1, 0}
};
for (int[] offset: offsets) {
int rowOffset = offset[0];
int colOffset = offset[1];
result = depthFirstSearch(row + rowOffset, col + colOffset, word, index + 1, grid);
if (result) {
break;
}
}
grid[row][col] = temp;
return result;
}
public static void printGrid(char[][] grid) {
for (int i = 0; i < grid.length; i++) {
System.out.print("\t\t[");
for (int j = 0; j < grid[0].length; j++) {
if (j < grid[0].length - 1)
System.out.print("'" + grid[i][j] + "', ");
else
System.out.print("'" + grid[i][j] + "'");
}
System.out.println("]");
}
System.out.println("\n");
}
public static void main( String args[] ) {
char[][][] grids = {
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'U', 'F', 'M', 'Q'},
{'I', 'C', 'Q', 'R', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'E', 'D', 'X', 'I', 'W'},
{'P', 'A', 'F', 'M', 'Q'},
{'I', 'C', 'A', 'S', 'F'},
{'M', 'A', 'L', 'C', 'A'},
{'J', 'T', 'I', 'V', 'E'}},
{{'h', 'e', 'c', 'm', 'l'},
{'w', 'l', 'i', 'e', 'u'},
{'a', 'r', 'r', 's', 'n'},
{'s', 'i', 'i', 'o', 'r'}},
{{'C', 'Q', 'N', 'A'},
{'P', 'S', 'E', 'I'},
{'Z', 'A', 'P', 'E'},
{'J', 'V', 'T', 'K'}},
{{'O', 'Y', 'O', 'I'},
{'B', 'Y', 'N', 'M'},
{'K', 'D', 'A', 'R'},
{'C', 'I', 'M', 'I'},
{'Z', 'I', 'T', 'O'}}
};
String[] words = {"EDUCATIVE", "PACANS", "warrior", "SAVE", "DYNAMIC"};
for(int i=0;i<words.length;i++){
System.out.print(i+1);
System.out.println(".\tGrid = ");
printGrid(grids[i]);
System.out.println("\tWord = "+ words[i]);
Boolean result = wordSearch(grids[i], words[i]);
if(result == true){
System.out.println("\n\tSearch result = Found Word");
}
else{
System.out.println("\n\tSearch result = Word could not be found");
}
System.out.println(new String(new char[100]).replace('\0', '-'));
}
}
}
Word Search

Solution summary

To recap, the solution to this problem can be divided into the following parts:

  • Check the base case to see if the whole word has been found in the grid.
  • Check if the current cell is out of bounds or doesn’t contain the required character.
  • Explore the four neighboring cells to see if the next character of the word can be found.
  • Repeat the process from the neighboring cell until the word is found or the entire grid is traversed.

Time complexity

In the depthFirstSearch function, we initially have four directions to explore. However, there are only three choices left in each cell afterward because one has already been explored. Therefore, the time complexity of the algorithm above is O(c×3l)O(c \times 3^l), where cc is the number of cells and ll is the length of the word we are searching for.

Space complexity

The space complexity is O(l)O(l), where ll is the length of the word to be searched in the grid. This is the maximum depth of the recursive call tree at any point during the search.

Level up your interview prep. Join Educative to access 70+ hands-on prep courses.