Taro Logo

Sudoku Solver

Medium
3 views
2 months ago

Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must satisfy all of the following rules:

  1. Each of the digits 1-9 must occur exactly once in each row.
  2. Each of the digits 1-9 must occur exactly once in each column.
  3. Each of the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

The '.' character indicates empty cells.

For example:

Input:

board = [
    ["5","3",".",".","7",".",".",".","."],
    ["6",".",".","1","9","5",".",".","."],
    [".","9","8",".",".",".",".","6","."],
    ["8",".",".",".","6",".",".",".","3"],
    ["4",".",".","8",".","3",".",".","1"],
    ["7",".",".",".","2",".",".",".","6"],
    [".","6",".",".",".",".","2","8","."],
    [".",".",".","4","1","9",".",".","5"],
    [".",".",".",".","8",".",".","7","9"]
]

Output:

[
    ["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"]
]

Constraints:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] is a digit or '.'.
  • It is guaranteed that the input board has only one solution.
Sample Answer
## Solving Sudoku Puzzles with Backtracking

This program solves Sudoku puzzles by implementing a backtracking algorithm. The core idea is to iterate through the empty cells and try filling them with numbers 1-9. If a number violates Sudoku rules, we backtrack and try another number. If we find a valid number, we move to the next empty cell and repeat the process.  The algorithm continues until the entire board is filled according to Sudoku rules, or we have exhausted all possibilities, implying no solution exists.

### Code:

```python
def solve_sudoku(board):
    """Solves a Sudoku puzzle using backtracking."""
    def find_empty_location(board):
        for i in range(9):
            for j in range(9):
                if board[i][j] == ".":
                    return (i, j)
        return None

    def is_valid(board, num, pos):
        # Check row
        for i in range(9):
            if board[pos[0]][i] == num and pos[1] != i:
                return False

        # Check column
        for i in range(9):
            if board[i][pos[1]] == num and pos[0] != i:
                return False

        # Check 3x3 box
        box_x = pos[1] // 3
        box_y = pos[0] // 3

        for i in range(box_y * 3, box_y * 3 + 3):
            for j in range(box_x * 3, box_x * 3 + 3):
                if board[i][j] == num and (i,j) != pos:
                    return False

        return True

    def solve():
        empty_location = find_empty_location(board)
        if not empty_location:
            return True  # No more empty locations, puzzle solved

        row, col = empty_location

        for num in range(1, 10):
            num = str(num)
            if is_valid(board, num, (row, col)):
                board[row][col] = num

                if solve():
                    return True

                board[row][col] = "."  # Backtrack

        return False  # No solution found for this empty location

    solve()
    return board


# Example Usage:
board = [
    ["5","3",".",".","7",".",".",".","."],
    ["6",".",".","1","9","5",".",".","."],
    [".","9","8",".",".",".",".","6","."],
    ["8",".",".",".","6",".",".",".","3"],
    ["4",".",".","8",".","3",".",".","1"],
    ["7",".",".",".","2",".",".",".","6"],
    [".","6",".",".",".",".","2","8","."],
    [".",".",".","4","1","9",".",".","5"],
    [".",".",".",".","8",".",".","7","9"]
]

solved_board = solve_sudoku(board)

for row in solved_board:
    print(row)

Naive Approach (Brute Force)

The most straightforward approach would be to try every possible combination of numbers in the empty cells. This means filling the first empty cell with a 1, checking if the board is valid, then trying 2, 3, up to 9. If none of them are valid, you backtrack. If a number is valid, you move on to the next empty cell and repeat the process. The issue with this method is that it's incredibly inefficient due to the massive number of possible combinations.

Optimal Approach (Backtracking)

The backtracking algorithm significantly reduces the search space. Instead of exhaustively checking all combinations, it intelligently explores possibilities. When a conflict (invalid placement) is detected, the algorithm immediately backtracks to the previous decision point, avoiding the exploration of entire sub-trees of invalid combinations. This pruning strategy makes the backtracking algorithm much more efficient than a brute-force approach.

Big(O) Runtime Analysis

The time complexity of the backtracking algorithm for solving Sudoku is difficult to precisely determine, but it's often approximated as O(9^E), where E is the number of empty cells. Here's why:

  • Branching Factor: For each empty cell, there are potentially 9 choices (numbers 1 to 9).
  • Depth: In the worst case, we might have to explore all possible combinations of numbers in the empty cells before finding the correct solution or determining that no solution exists.
  • Pruning: The backtracking algorithm prunes the search space by checking the validity of each number placement against Sudoku rules (row, column, and 3x3 subgrid constraints). This pruning is what makes backtracking more efficient than brute force, but the worst-case complexity remains exponential.

While the O(9^E) is a common approximation, the actual runtime depends heavily on the structure of the puzzle and the effectiveness of the constraints in reducing the search space. Well-designed Sudoku puzzles that provide more initial clues can be solved much faster.

Big(O) Space Usage Analysis

The space complexity of the backtracking algorithm is primarily determined by the call stack during the recursive calls. Here's the analysis:

  • Call Stack Depth: The maximum depth of the recursion is proportional to the number of empty cells on the board. In the worst case, all cells except the initially filled ones could be empty.
  • Space per Call: Each recursive call requires storing the current state of the board (or a copy of it), the row and column indices of the empty cell being considered, and the number being tried.

Therefore, the space complexity can be estimated as O(E), where E is the number of empty cells. In the worst case, E could be close to 81 (all cells empty), but in typical Sudoku puzzles, E is significantly smaller.

Edge Cases and Considerations

  1. No Solution: The algorithm should correctly identify when a Sudoku puzzle has no solution. This happens when, at some point, no number from 1 to 9 can be placed in an empty cell without violating the Sudoku rules. The algorithm backtracks and eventually returns False.
  2. Multiple Solutions: Although the problem statement guarantees a single solution, a robust implementation could be modified to find all possible solutions (though this would significantly increase the runtime).
  3. Invalid Input: The input board should be validated to ensure it conforms to the Sudoku rules before starting the solving process. This includes checking for duplicate numbers in rows, columns, and subgrids in the initial state.
  4. Pre-filled Cells: The algorithm must correctly handle pre-filled cells by skipping them during the search for empty locations and respecting their values when checking the validity of number placements.
  5. Performance Optimization: For very difficult Sudoku puzzles, techniques like constraint propagation (e.g., eliminating possible numbers for a cell based on the values in its row, column, and subgrid) can be used to further reduce the search space and improve performance.