diff --git a/DIRECTORY.md b/DIRECTORY.md index ee09790ed64d..476ce0866966 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -19,6 +19,7 @@ * [Permutation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/Permutation.java) * [PowerSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/PowerSum.java) * [SubsequenceFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/SubsequenceFinder.java) + * [SudokuSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java) * [WordSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordSearch.java) * bitmanipulation * [BitSwap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java) @@ -593,6 +594,7 @@ * [PermutationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PermutationTest.java) * [PowerSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PowerSumTest.java) * [SubsequenceFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/SubsequenceFinderTest.java) + * [SudokuSolverTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java) * [WordSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordSearchTest.java) * bitmanipulation * [BitSwapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java) 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..255816b68612 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java @@ -0,0 +1,175 @@ +package com.thealgorithms.backtracking; + +/** + * Java program for Sudoku Solver using Backtracking Algorithm. + * Solves a given Sudoku puzzle by filling the empty cells (denoted by '0') with + * valid digits (1-9). + */ +public final class SudokuSolver { + private SudokuSolver() { + } + + /** + * Check if placing a number in a specific cell is valid. + * Validity is checked based on the row, column, and 3x3 subgrid. + * + * @param board The 9x9 Sudoku board + * @param row The row index + * @param col The column index + * @param num The number to be placed in the cell + * @return true if placing the number is valid, false otherwise + */ + public static boolean isValidMove(final int[][] board, final int row, final int col, final int num) { + // Check the row + for (int x = 0; x < 9; x++) { + if (board[row][x] == num) { + return false; + } + } + + // Check the column + for (int x = 0; x < 9; x++) { + if (board[x][col] == num) { + return false; + } + } + + // Check the 3x3 sub-grid + int startRow = row / 3 * 3; + int startCol = col / 3 * 3; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (board[startRow + i][startCol + j] == num) { + return false; + } + } + } + + return true; + } + + /** + * Solve the Sudoku puzzle using backtracking. + * + * @param board The 9x9 Sudoku board + * @return true if the puzzle is solved, false if no solution exists + */ + public static boolean solveSudokuHelper(final int[][] board) { + // Traverse the board + for (int row = 0; row < 9; row++) { + for (int col = 0; col < 9; col++) { + // Empty cell found + if (board[row][col] == 0) { + // Try placing numbers 1-9 in the empty cell + for (int num = 1; num <= 9; num++) { + if (isValidMove(board, row, col, num)) { + board[row][col] = num; + + // Recursively try to solve the board with the new number + if (solveSudoku(board)) { + return true; // Solution found + } + board[row][col] = 0; // Backtrack + } + } + return false; // No valid number can be placed in this cell + } + } + } + + return true; // Sudoku is solved if no empty cells are left + } + + /** + * Check if the given Sudoku board is valid. + * A valid board has no duplicate numbers in rows, columns, or 3x3 subgrids. + * + * @param board The 9x9 Sudoku board + * @return true if the board is valid, false otherwise + */ + public static boolean isValidSudoku(final int[][] board) { + // Check each row + for (int i = 0; i < 9; i++) { + boolean[] row = new boolean[10]; + for (int j = 0; j < 9; j++) { + if (board[i][j] != 0) { + if (row[board[i][j]]) { + return false; + } + row[board[i][j]] = true; + } + } + } + + // Check each column + for (int i = 0; i < 9; i++) { + boolean[] col = new boolean[10]; + for (int j = 0; j < 9; j++) { + if (board[j][i] != 0) { + if (col[board[j][i]]) { + return false; + } + col[board[j][i]] = true; + } + } + } + + // Check each 3x3 subgrid + for (int i = 0; i < 9; i += 3) { + for (int j = 0; j < 9; j += 3) { + boolean[] grid = new boolean[10]; + for (int k = 0; k < 3; k++) { + for (int l = 0; l < 3; l++) { + if (board[i + k][j + l] != 0) { + if (grid[board[i + k][j + l]]) { + return false; + } + grid[board[i + k][j + l]] = true; + } + } + } + } + } + + return true; + } + + public static boolean solveSudoku(final int[][] board) { + // Check if the board is valid + if (!isValidSudoku(board)) { + return false; + } + + return solveSudokuHelper(board); + } + + /** + * Print the current state of the Sudoku board. + * + * @param board The 9x9 Sudoku board + */ + public static void printBoard(final int[][] board) { + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + System.out.print(board[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + // Example 9x9 Sudoku board with some cells pre-filled and some cells empty (0) + 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); + + // Solve the Sudoku puzzle + if (solveSudoku(board)) { + System.out.println("\nSolved Sudoku Board:"); + printBoard(board); + } else { + System.out.println("\nNo solution exists for the given Sudoku puzzle."); + } + } +} 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..42b69ae50640 --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java @@ -0,0 +1,61 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test class for SudokuSolver using JUnit 5. + * This class verifies the correctness of the Sudoku solver algorithm. + */ +class SudokuSolverTest { + + /** + * Test case for solving a solvable Sudoku puzzle. + */ + @Test + void testForValidSudoku() { + 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}}; + + // Solve the Sudoku puzzle + boolean solved = SudokuSolver.solveSudoku(board); + + // Check that the puzzle is solvable + assertTrue(solved, "The Sudoku puzzle should be solvable."); + + // Expected output for a solved puzzle + 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}}; + + // Verify the solved board matches the expected result + assertArrayEquals(expected, board); + } + + /** + * Test case for an already solved Sudoku puzzle. + */ + @Test + void testForAlreadySolvedSudoku() { + 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}}; + + // Check that the board is already solved + boolean solved = SudokuSolver.solveSudoku(board); + + // Check that no changes are made to the already solved board + assertTrue(solved, "The Sudoku board should be already solved."); + } + + /** + * Test case for solving an empty Sudoku puzzle (no empty cells). + */ + @Test + void testForEmptySudoku() { + 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}}; + + // Solve the "already filled" puzzle + boolean solved = SudokuSolver.solveSudoku(board); + + // Check that the board is already solved + assertTrue(solved, "The Sudoku board should be solved (no empty cells)."); + } +}