Recursion and Backtracking in Java

Mastering recursion and backtracking techniques in Java, with examples like the N-Queens problem and Sudoku solver.


Sudoku Solver in Java: Recursion and Backtracking

Understanding Sudoku

Sudoku is a logic-based number-placement puzzle. The objective is to fill a 9×9 grid with digits so that each column, each row, and each of the nine 3×3 subgrids that compose the grid (also called "boxes", "blocks", or "regions") contains all of the digits from 1 to 9. The puzzle setter provides a partially completed grid, which for a well-posed puzzle has a unique solution.

The Sudoku Solving Algorithm: Recursion and Backtracking

We will implement a Sudoku solver using a recursive backtracking algorithm. The basic idea is:

  1. Find an Empty Cell: Locate an empty cell in the grid (represented by 0 in our code).
  2. Try Numbers 1-9: For each number from 1 to 9, check if it's a valid placement in that empty cell (i.e., it doesn't violate Sudoku rules).
  3. Recursive Call: If the number is valid, place it in the cell and recursively call the solver to fill the rest of the grid.
  4. Backtrack: If the recursive call doesn't lead to a solution (the placement leads to an invalid state later on), reset the cell back to 0 and try the next number.
  5. Solution Found: If the grid is completely filled (no more empty cells), we have found a solution.

Java Code Implementation

Here's the Java code for the Sudoku solver:

 public class SudokuSolver {

    public static boolean solveSudoku(int[][] board) {
        for (int row = 0; row < 9; row++) {
            for (int col = 0; col < 9; col++) {
                if (board[row][col] == 0) {
                    for (int number = 1; number <= 9; number++) {
                        if (isValidPlacement(board, number, row, col)) {
                            board[row][col] = number;

                            if (solveSudoku(board)) {
                                return true; // Solution found
                            } else {
                                board[row][col] = 0; // Backtrack
                            }
                        }
                    }
                    return false; // No valid number found for this cell
                }
            }
        }
        return true; // Sudoku is solved
    }

    private static boolean isValidPlacement(int[][] board, int number, int row, int col) {
        // Check row
        for (int i = 0; i < 9; i++) {
            if (board[row][i] == number) {
                return false;
            }
        }

        // Check column
        for (int i = 0; i < 9; i++) {
            if (board[i][col] == number) {
                return false;
            }
        }

        // Check 3x3 box
        int boxRow = row - row % 3;
        int boxCol = col - col % 3;

        for (int i = boxRow; i < boxRow + 3; i++) {
            for (int j = boxCol; j < boxCol + 3; j++) {
                if (board[i][j] == number) {
                    return false;
                }
            }
        }

        return true; // Valid placement
    }

    public static void printBoard(int[][] board) {
        for (int row = 0; row < 9; row++) {
            if (row % 3 == 0 && row != 0) {
                System.out.println("-----------");
            }
            for (int col = 0; col < 9; col++) {
                if (col % 3 == 0 && col != 0) {
                    System.out.print("|");
                }
                System.out.print(board[row][col] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[][] board = {
                {5, 3, 0, 0, 7, 0, 0, 0, 0},
                {6, 0, 0, 1, 9, 5, 0, 0, 0},
                {0, 9, 8, 0, 0, 0, 0, 6, 0},
                {8, 0, 0, 0, 6, 0, 0, 0, 3},
                {4, 0, 0, 8, 0, 3, 0, 0, 1},
                {7, 0, 0, 0, 2, 0, 0, 0, 6},
                {0, 6, 0, 0, 0, 0, 2, 8, 0},
                {0, 0, 0, 4, 1, 9, 0, 0, 5},
                {0, 0, 0, 0, 8, 0, 0, 7, 9}
        };

        System.out.println("Original Sudoku Board:");
        printBoard(board);

        if (solveSudoku(board)) {
            System.out.println("\nSolved Sudoku Board:");
            printBoard(board);
        } else {
            System.out.println("\nNo solution exists.");
        }
    }
} 

Explanation:

  • solveSudoku(int[][] board): This is the main recursive function that attempts to solve the Sudoku.
  • isValidPlacement(int[][] board, int number, int row, int col): Checks if placing the given number at (row, col) is valid according to Sudoku rules. It checks the row, column, and 3x3 box.
  • printBoard(int[][] board): A utility function to print the Sudoku board in a readable format.
  • The main method creates an example Sudoku board, calls the solver, and prints the result.

Algorithm Explanation:

Finding the Empty Cell

The algorithm iterates through each cell of the Sudoku grid. The first step in each recursive call is to find an empty cell. An empty cell is represented by a 0 in the board array.

Valid Number Placement

For each empty cell found, the algorithm tries placing numbers from 1 to 9. Before placing a number, the isValidPlacement function is called. This function verifies if placing the number in the current cell violates any of the Sudoku rules:

  • Row Check: The function checks if the number already exists in the same row.
  • Column Check: The function checks if the number already exists in the same column.
  • 3x3 Box Check: The function checks if the number already exists in the 3x3 box that the cell belongs to. This involves calculating the starting row and column index of the box and iterating through all cells within the box.

Recursion and Backtracking

If a number is valid, it is placed in the cell. Then, the solveSudoku function is called recursively. This simulates "trying" the number and seeing if it leads to a complete solution. If the recursive call returns true, it means a solution has been found, and the function returns true, propagating the solution up the call stack.

If the recursive call returns false, it means the current placement of the number leads to a dead end. This is where backtracking comes in. The number is removed from the cell (set back to 0), and the next number is tried. If all numbers from 1 to 9 have been tried and none lead to a solution, the function returns false, signaling that the previous placement was incorrect and needs to be re-evaluated.

Base Case

The recursion stops when all cells are filled. If the algorithm reaches a state where there are no more empty cells (i.e., the outer loops in solveSudoku complete without finding a 0), it means the Sudoku has been solved, and the function returns true.

Optimization (Competitive Programming Considerations)

While the above code works, here are some optimizations that can be crucial in competitive programming, especially when dealing with extremely large grids or time constraints:

  • Pre-calculate Valid Moves: Instead of re-calculating valid moves for each cell on every recursive call, you could pre-calculate and store possible numbers for each cell. This avoids redundant calculations.
  • Bit Manipulation: Use bit manipulation to represent the possible values for each cell and to efficiently perform checks. This can be significantly faster than iterating through loops.
  • Minimum Remaining Values (MRV) Heuristic: Instead of iterating through the grid sequentially to find an empty cell, choose the empty cell with the fewest possible values. This often leads to faster convergence.
  • Forward Checking: After assigning a value to a cell, proactively eliminate that value from the possible values of its related cells (cells in the same row, column, or 3x3 box). This reduces the search space.
  • Constraint Propagation: More advanced constraint propagation techniques can be used to further reduce the search space.

Here's an example showing how you might use bit manipulation for checking validity:

 private static boolean isValidPlacementBitwise(int[][] board, int number, int row, int col) {
    // Convert number to its bit representation (1 << (number - 1))
    int numberBit = 1 << (number - 1);

    // Check row
    for (int i = 0; i < 9; i++) {
        if (board[row][i] != 0 && (1 << (board[row][i] - 1)) == numberBit) {
            return false;
        }
    }

    // Check column
    for (int i = 0; i < 9; i++) {
        if (board[i][col] != 0 && (1 << (board[i][col] - 1)) == numberBit) {
            return false;
        }
    }

    // Check 3x3 box
    int boxRow = row - row % 3;
    int boxCol = col - col % 3;

    for (int i = boxRow; i < boxRow + 3; i++) {
        for (int j = boxCol; j < boxCol + 3; j++) {
             if (board[i][j] != 0 && (1 << (board[i][j] - 1)) == numberBit) {
                return false;
            }
        }
    }

    return true;
} 

This `isValidPlacementBitwise` function demonstrates how to represent numbers using bits. This technique can lead to faster comparisons.

Example Sudoku Grid

Here's how the Sudoku grid is represented using HTML and CSS. Note the use of the bold-border-right and bold-border-bottom classes to visually distinguish the 3x3 blocks:

5
3
0
0
7
0
0
0
0
6
0
0
1
9
5
0
0
0
0
9
8
0
0
0
0
6
0
8
0
0
0
6
0
0
0
3
4
0
0
8
0
3
0
0
1
7
0
0
0
2
0
0
0
6
0
6
0
0
0
0
2
8
0
0
0
0
4
1
9
0
0
5
0
0
0
0
8
0
0
7
9