Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must satisfy all of the following rules:
1-9
must occur exactly once in each row.1-9
must occur exactly once in each column.1-9
must occur exactly once in each of the 9 3x3
sub-boxes of the grid.The .
character indicates empty cells.
For example, given the following 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"]
]
The program should return the solved Sudoku 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"]
]
Constraints:
board.length == 9
board[i].length == 9
board[i][j]
is a digit or .
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach to solving Sudoku is like trying every possible combination of numbers until you find one that works. We fill in the empty spaces one by one, checking if our choice breaks any Sudoku rules. If it does, we backtrack and try a different number.
Here's how the algorithm would work step-by-step:
def solve_sudoku(board):
def is_valid(board, number, position):
row_of_board = position[0]
column_of_board = position[1]
# Check row
for index in range(len(board[0])):
if board[row_of_board][index] == number and column_of_board != index:
return False
# Check column
for index in range(len(board)):
if board[index][column_of_board] == number and row_of_board != index:
return False
# Check 3x3 box
box_x = column_of_board // 3
box_y = row_of_board // 3
for index_i in range(box_y * 3, box_y * 3 + 3):
for index_j in range(box_x * 3, box_x * 3 + 3):
if board[index_i][index_j] == number and (index_i, index_j) != position:
return False
return True
def find_empty(board):
for row_of_board in range(len(board)):
for column_of_board in range(len(board[0])):
if board[row_of_board][column_of_board] == 0:
return (row_of_board, column_of_board) # row, col
return None
def solve():
empty_spot = find_empty(board)
if not empty_spot:
return True
else:
row_of_board, column_of_board = empty_spot
# Iterate to try each number
for number in range(1, 10):
if is_valid(board, number, (row_of_board, column_of_board)):
board[row_of_board][column_of_board] = number
# Recursively attempt to solve from this state
if solve():
return True
# Reset the cell if the current number doesn't lead to a solution.
board[row_of_board][column_of_board] = 0
# Trigger backtracking if no number works for the current cell
return False
solve()
return board
The Sudoku Solver problem can be efficiently solved using a strategy called backtracking. We try placing numbers one by one, and if a placement leads to a conflict, we undo it and try a different number. This avoids exploring every single possibility, making the process much faster.
Here's how the algorithm would work step-by-step:
def solve_sudoku(board):
def find_empty_cell(board):
for row in range(9):
for col in range(9):
if board[row][col] == '.':
return (row, col)
return None
def is_valid(board, number, position):
row_position = position[0]
column_position = position[1]
# Check row
for i in range(9):
if board[row_position][i] == str(number) and column_position != i:
return False
# Check column
for i in range(9):
if board[i][column_position] == str(number) and row_position != i:
return False
# Check 3x3 box
box_x = (column_position // 3) * 3
box_y = (row_position // 3) * 3
for i in range(box_y, box_y + 3):
for j in range(box_x, box_x + 3):
if board[i][j] == str(number) and (i, j) != position:
return False
return True
def solve():
empty_cell = find_empty_cell(board)
if not empty_cell:
return True
else:
row, col = empty_cell
for number in range(1, 10):
# We check if placing the current number is valid
if is_valid(board, number, (row, col)):
board[row][col] = str(number)
# Attempt to solve the board recursively
if solve():
return True
# Backtrack if the current placement is wrong
board[row][col] = '.'
# Triggered when no number can solve the cell
return False
solve()
Case | How to Handle |
---|---|
Null or empty board input | Return immediately or throw an IllegalArgumentException if the board is null or empty, as there's nothing to solve. |
Invalid board dimensions (not 9x9) | Throw an IllegalArgumentException if the board's dimensions are not 9x9, as the Sudoku rules are defined for that size. |
Board contains invalid characters (other than digits 1-9 and '.') | Throw an IllegalArgumentException if the board contains characters other than digits 1-9 and '.', indicating an invalid input. |
Board is already completely solved and valid | The backtracking algorithm should still execute and correctly identify it as a solved state without altering it. |
Board has no solution | The backtracking algorithm should exhaust all possibilities and return false, leaving the board in its initial, unsolvable state. |
Board has multiple possible solutions; only one solution needs to be found | The backtracking algorithm stops upon finding the first valid solution, as only one solution is required. |
Board contains a row, column, or 3x3 subgrid that is already invalid (duplicate numbers) | The backtracking algorithm will immediately return false upon encountering this invalid state, preventing further unnecessary exploration. |
Potential stack overflow due to excessive recursion depth in backtracking | Ensure the base cases (board solved or no valid moves) are handled effectively to prevent infinite recursion and potential stack overflow. |