Loading problems...
TL;DR: The optimal solution uses backtracking to place numbers 1-9 in empty cells, validating constraints immediately and reverting changes (backtracking) whenever a violation occurs or a dead end is reached.
The Sudoku Solver problem asks us to complete a partially filled grid. The objective is to fill every empty cell (denoted by '.') with a digit from 1 to 9 such that every row, every column, and every sub-box contains each digit exactly once. This is a classic example of a Constraint Satisfaction Problem (CSP) often used as a standard interview question to test recursion and state-space search skills.
A naive brute force approach attempts to generate every possible configuration of the board until a valid solution is found.
Pseudo-code:
textfunction bruteForce(index): if index == number_of_empty_cells: return isValidBoard() for digit in '1' to '9': fill cell[index] with digit if bruteForce(index + 1): return true return false
Why it fails: The time complexity is , where is the number of empty cells. If a board has 50 empty cells, the algorithm attempts up to combinations. This approach fails due to Time Limit Exceeded because it fills the entire board before checking validity. It wastes massive computational resources exploring branches that are obviously invalid (e.g., placing two '5's next to each other early in the process).
The core insight that optimizes the naive approach is pruning. Instead of validating the board only after it is full, we validate the board before making a placement.
The invariant we must maintain is: At every step of the recursion, the board must remain valid according to Sudoku rules.
If we are at cell and decide to place the number '5', we immediately check:
If the answer to any of these is "yes," we do not place '5' and do not recurse further down that path. We prune the tree immediately. This drastically reduces the search space from to a much smaller subset of valid configurations.
Visual Description: Imagine the algorithm as traversing a decision tree. The root is the initial board. At the first empty cell, the tree splits into up to 9 branches (digits 1-9).
![]()
Let's trace the logic for a simplified scenario:
(0, 2).(0, 2).solve() again.
(0, 3).(0, 3).(0, 3) returns false.
(0, 2).(0, 2) from '2' back to '.'.(0, 2) trying digit '3'.This process continues until the board is completely filled.
The algorithm implements a Depth-First Search (DFS) on the state space of possible Sudoku boards.
isValid check ensures that we never build upon an invalid state. Therefore, if we reach a state with no empty cells, it must be a valid solution.cppclass Solution { public: void solveSudoku(vector<vector<char>>& board) { solve(board); } private: bool solve(vector<vector<char>>& board) { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { // Find the first empty cell if (board[i][j] == '.') { // Try digits 1-9 for (char c = '1'; c <= '9'; c++) { if (isValid(board, i, j, c)) { board[i][j] = c; // Place choice if (solve(board)) // Recurse return true; // Found solution board[i][j] = '.'; // Backtrack } } return false; // No digit worked for this cell } } } return true; // All cells filled } bool isValid(vector<vector<char>>& board, int row, int col, char c) { for (int i = 0; i < 9; i++) { // Check row if (board[row][i] == c) return false; // Check column if (board[i][col] == c) return false; // Check 3x3 sub-box // Formula to map linear index i (0-8) to sub-box coordinates if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c) return false; } return true; } };
javaclass Solution { public void solveSudoku(char[][] board) { solve(board); } private boolean solve(char[][] board) { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { // Look for empty cell if (board[i][j] == '.') { // Try placing digits 1-9 for (char c = '1'; c <= '9'; c++) { if (isValid(board, i, j, c)) { board[i][j] = c; // Place if (solve(board)) { return true; // Solution found } board[i][j] = '.'; // Backtrack } } return false; // No valid placement found, trigger backtrack } } } return true; // Board is full } private boolean isValid(char[][] board, int row, int col, char c) { for (int i = 0; i < 9; i++) { // Check Row if (board[row][i] == c) return false; // Check Column if (board[i][col] == c) return false; // Check 3x3 Block int blkRow = 3 * (row / 3) + i / 3; int blkCol = 3 * (col / 3) + i % 3; if (board[blkRow][blkCol] == c) return false; } return true; } }
pythonclass Solution: def solveSudoku(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place. """ self.solve(board) def solve(self, board): for i in range(9): for j in range(9): if board[i][j] == '.': for char in "123456789": if self.isValid(board, i, j, char): board[i][j] = char # Place if self.solve(board): # Recurse return True board[i][j] = '.' # Backtrack return False return True def isValid(self, board, row, col, char): for i in range(9): # Check row if board[row][i] == char: return False # Check column if board[i][col] == char: return False # Check 3x3 block # Logic: (row // 3) * 3 gives the starting row index of the block # i // 3 iterates 0, 0, 0, 1, 1, 1, 2, 2, 2 blk_row = 3 * (row // 3) + i // 3 blk_col = 3 * (col // 3) + i % 3 if board[blk_row][blk_col] == char: return False return True
Time Complexity: Where is the number of empty cells. In the worst case, for each empty cell, we have 9 choices. However, due to aggressive pruning (constraint satisfaction), the actual runtime is significantly faster than the theoretical upper bound. For a fixed board, one could argue the complexity is (constant) because the input size doesn't grow, but in the context of backtracking algorithms, we express it relative to the search space depth.
Space Complexity: This represents the space required for the recursion stack. In the worst case, the recursion depth equals the number of empty cells (which is at most 81). We modify the board in place, so no auxiliary data structures proportional to the grid size are strictly necessary beyond the stack.
The Backtracking - N-Queens / Constraint Satisfaction pattern used here is directly applicable to several other popular interview questions:
Why does my solution get Time Limit Exceeded (TLE)?
true immediately when a solution is found.true immediately after the recursive call succeeds, the algorithm continues to try other numbers even after the board is solved.Why is the board not resetting correctly?
board[i][j] = '.'.How do I calculate the 3x3 sub-box index?
if/else logic to determine the sub-box boundaries.(row / 3) * 3 to find the top-left corner of the block. Iterate k from 0 to 8, accessing board[startRow + k/3][startCol + k%3].