diff --git a/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java new file mode 100644 index 000000000000..543fe2d02b50 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java @@ -0,0 +1,157 @@ +package com.thealgorithms.backtracking; + +/** + * Sudoku Solver using Backtracking Algorithm + * Solves a 9x9 Sudoku puzzle by filling empty cells with valid digits (1-9) + * + * @author Navadeep0007 + */ +public final class SudokuSolver { + + private static final int GRID_SIZE = 9; + private static final int SUBGRID_SIZE = 3; + private static final int EMPTY_CELL = 0; + + private SudokuSolver() { + // Utility class, prevent instantiation + } + + /** + * Solves the Sudoku puzzle using backtracking + * + * @param board 9x9 Sudoku board with 0 representing empty cells + * @return true if puzzle is solved, false otherwise + */ + public static boolean solveSudoku(int[][] board) { + if (board == null || board.length != GRID_SIZE) { + return false; + } + + for (int row = 0; row < GRID_SIZE; row++) { + if (board[row].length != GRID_SIZE) { + return false; + } + } + + return solve(board); + } + + /** + * Recursive helper method to solve the Sudoku puzzle + * + * @param board the Sudoku board + * @return true if solution is found, false otherwise + */ + private static boolean solve(int[][] board) { + for (int row = 0; row < GRID_SIZE; row++) { + for (int col = 0; col < GRID_SIZE; col++) { + if (board[row][col] == EMPTY_CELL) { + for (int number = 1; number <= GRID_SIZE; number++) { + if (isValidPlacement(board, row, col, number)) { + board[row][col] = number; + + if (solve(board)) { + return true; + } + + // Backtrack + board[row][col] = EMPTY_CELL; + } + } + return false; + } + } + } + return true; + } + + /** + * Checks if placing a number at given position is valid + * + * @param board the Sudoku board + * @param row row index + * @param col column index + * @param number number to place (1-9) + * @return true if placement is valid, false otherwise + */ + private static boolean isValidPlacement(int[][] board, int row, int col, int number) { + return !isNumberInRow(board, row, number) && !isNumberInColumn(board, col, number) && !isNumberInSubgrid(board, row, col, number); + } + + /** + * Checks if number exists in the given row + * + * @param board the Sudoku board + * @param row row index + * @param number number to check + * @return true if number exists in row, false otherwise + */ + private static boolean isNumberInRow(int[][] board, int row, int number) { + for (int col = 0; col < GRID_SIZE; col++) { + if (board[row][col] == number) { + return true; + } + } + return false; + } + + /** + * Checks if number exists in the given column + * + * @param board the Sudoku board + * @param col column index + * @param number number to check + * @return true if number exists in column, false otherwise + */ + private static boolean isNumberInColumn(int[][] board, int col, int number) { + for (int row = 0; row < GRID_SIZE; row++) { + if (board[row][col] == number) { + return true; + } + } + return false; + } + + /** + * Checks if number exists in the 3x3 subgrid + * + * @param board the Sudoku board + * @param row row index + * @param col column index + * @param number number to check + * @return true if number exists in subgrid, false otherwise + */ + private static boolean isNumberInSubgrid(int[][] board, int row, int col, int number) { + int subgridRowStart = row - row % SUBGRID_SIZE; + int subgridColStart = col - col % SUBGRID_SIZE; + + for (int i = subgridRowStart; i < subgridRowStart + SUBGRID_SIZE; i++) { + for (int j = subgridColStart; j < subgridColStart + SUBGRID_SIZE; j++) { + if (board[i][j] == number) { + return true; + } + } + } + return false; + } + + /** + * Prints the Sudoku board + * + * @param board the Sudoku board + */ + public static void printBoard(int[][] board) { + for (int row = 0; row < GRID_SIZE; row++) { + if (row % SUBGRID_SIZE == 0 && row != 0) { + System.out.println("-----------"); + } + for (int col = 0; col < GRID_SIZE; col++) { + if (col % SUBGRID_SIZE == 0 && col != 0) { + System.out.print("|"); + } + System.out.print(board[row][col]); + } + System.out.println(); + } + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java new file mode 100644 index 000000000000..75d3eae08629 --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class SudokuSolverTest { + + @Test + void testSolveSudokuEasyPuzzle() { + 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}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + + int[][] expected = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + + assertArrayEquals(expected, board); + } + + @Test + void testSolveSudokuHardPuzzle() { + int[][] board = {{0, 0, 0, 0, 0, 0, 6, 8, 0}, {0, 0, 0, 0, 7, 3, 0, 0, 9}, {3, 0, 9, 0, 0, 0, 0, 4, 5}, {4, 9, 0, 0, 0, 0, 0, 0, 0}, {8, 0, 3, 0, 5, 0, 9, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 3, 6}, {9, 6, 0, 0, 0, 0, 3, 0, 8}, {7, 0, 0, 6, 8, 0, 0, 0, 0}, {0, 2, 8, 0, 0, 0, 0, 0, 0}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuAlreadySolved() { + int[][] board = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuInvalidSize() { + int[][] board = {{1, 2, 3}, {4, 5, 6}}; + assertFalse(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuNullBoard() { + assertFalse(SudokuSolver.solveSudoku(null)); + } + + @Test + void testSolveSudokuEmptyBoard() { + int[][] board = {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } +}