Facebook Pixel

37. Sudoku Solver

HardArrayHash TableBacktrackingMatrix
Leetcode Link

Problem Description

You need to write a program that solves a Sudoku puzzle by filling in all the empty cells on a 9x9 grid.

The puzzle is represented as a 9x9 board where:

  • Each cell contains either a digit from '1' to '9' or a '.' character
  • The '.' character represents an empty cell that needs to be filled

To solve the Sudoku puzzle correctly, your solution must satisfy these three rules:

  1. Row Rule: Each digit from 1 to 9 must appear exactly once in every row
  2. Column Rule: Each digit from 1 to 9 must appear exactly once in every column
  3. Sub-box Rule: Each digit from 1 to 9 must appear exactly once in each of the nine 3x3 sub-boxes that make up the grid

The 9x9 grid is divided into nine 3x3 sub-boxes like this:

  • Top-left 3x3 box: rows 0-2, columns 0-2
  • Top-middle 3x3 box: rows 0-2, columns 3-5
  • Top-right 3x3 box: rows 0-2, columns 6-8
  • And so on for the middle and bottom rows

Your task is to fill all empty cells (marked with '.') with appropriate digits from 1 to 9 such that the completed board satisfies all Sudoku rules. The solution modifies the input board in-place rather than returning a new board.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • No: While Sudoku involves a grid structure, it's not a traditional graph problem with nodes and edges. We're filling in values in a 2D array rather than traversing connections between nodes.

Need to solve for kth smallest/largest?

  • No: We're not looking for any kth smallest or largest element. We need to find a valid configuration that satisfies all Sudoku rules.

Involves Linked Lists?

  • No: The problem uses a 2D array/grid structure, not linked lists.

Does the problem have small constraints?

  • Yes: The Sudoku board is fixed at 9x9 (81 cells total), and we only have digits 1-9 to work with. Even with empty cells to fill, the search space is manageable compared to problems with unbounded inputs. The constraints are small enough that we can systematically try different combinations.

Brute force / Backtracking?

  • Yes: This is exactly what we need. We must try placing different digits in empty cells, check if they violate any Sudoku rules, and backtrack if we reach an invalid state. The solution requires exploring different possibilities until we find a valid complete board.

Conclusion: The flowchart correctly leads us to use a Backtracking approach for solving Sudoku. This makes sense because:

  1. We need to make a series of choices (which digit to place in each empty cell)
  2. Each choice must satisfy constraints (row, column, and 3x3 box rules)
  3. If we reach a dead end, we need to undo our choice and try a different digit
  4. We continue this process until we find a valid solution that fills all empty cells
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we look at solving a Sudoku puzzle manually, we typically start by finding empty cells and trying different numbers to see which ones work. If we place a number and later discover it leads to a conflict, we erase it and try a different number. This natural problem-solving approach directly translates to the backtracking algorithm.

The key insight is that we need to keep track of three things simultaneously:

  1. Which numbers have already been used in each row
  2. Which numbers have already been used in each column
  3. Which numbers have already been used in each 3x3 sub-box

By maintaining this information, we can quickly check if placing a digit v at position (i, j) is valid by verifying:

  • Has v already appeared in row i?
  • Has v already appeared in column j?
  • Has v already appeared in the 3x3 box containing (i, j)?

The 3x3 box index can be calculated using integer division: the box at position (i // 3, j // 3) contains the cell at (i, j).

Rather than checking the entire board every time we want to validate a placement, we use boolean arrays to track used numbers. For example, row[i][v] tells us if digit v+1 has been used in row i. This gives us O(1) validation checks.

The backtracking process follows this pattern:

  1. Find an empty cell
  2. Try digits 1-9 in that cell
  3. For each valid digit (not violating any rules), mark it as used in the corresponding row, column, and box
  4. Recursively solve the rest of the puzzle
  5. If we hit a dead end (no valid digits for a cell), backtrack by unmarking the digit and trying the next one
  6. If we successfully fill all empty cells, we've found the solution

The elegance of this approach is that it mirrors human intuition while being systematic enough to guarantee finding a solution if one exists.

Learn more about Backtracking patterns.

Solution Approach

The implementation uses backtracking with efficient state tracking through boolean arrays. Let's walk through the key components:

Data Structures:

  • row: A 9x9 boolean array where row[i][v] indicates if digit v+1 has been used in row i
  • col: A 9x9 boolean array where col[j][v] indicates if digit v+1 has been used in column j
  • block: A 3x3x9 boolean array where block[i//3][j//3][v] indicates if digit v+1 has been used in the corresponding 3x3 sub-box
  • t: A list storing the coordinates (i, j) of all empty cells that need to be filled
  • ok: A flag to indicate when we've found a valid solution

Initialization Phase:

  1. Create the three boolean tracking arrays initialized to False
  2. Traverse the entire board once:
    • For empty cells (marked with '.'), add their coordinates to list t
    • For filled cells, mark the digit as used in the corresponding row, column, and block arrays
    • Convert digit character to index: v = int(board[i][j]) - 1

DFS Backtracking Function: The dfs(k) function processes the k-th empty cell in our list:

  1. Base Case: If k == len(t), we've successfully filled all empty cells, so set ok = True and return

  2. Recursive Case: For the current empty cell at position (i, j) = t[k]:

    • Try each digit from 0 to 8 (representing digits 1-9)
    • Check validity: row[i][v] == col[j][v] == block[i//3][j//3][v] == False
    • If valid:
      • Mark the digit as used: row[i][v] = col[j][v] = block[i//3][j//3][v] = True
      • Place the digit on the board: board[i][j] = str(v + 1)
      • Recursively solve for the next empty cell: dfs(k + 1)
      • If not successful (ok is still False), backtrack by unmarking: row[i][v] = col[j][v] = block[i//3][j//3][v] = False
    • If ok becomes True, immediately return to preserve the solution

Key Optimizations:

  1. Pre-collecting all empty cells avoids repeatedly searching the board
  2. Using boolean arrays gives O(1) validity checks instead of scanning rows/columns/blocks
  3. The early termination with ok flag prevents unnecessary backtracking once a solution is found
  4. Index calculation i // 3 and j // 3 efficiently maps any cell to its 3x3 block

The solution modifies the board in-place, so when dfs(0) completes, the board contains the solved Sudoku puzzle.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through solving a small portion of a Sudoku puzzle to illustrate the backtracking approach. Consider this simplified 4x4 mini-Sudoku (using digits 1-4 with 2x2 sub-boxes) to demonstrate the concept:

Initial board:
[1, ., ., 4]
[., ., 3, .]
[., 3, ., .]
[4, ., ., 1]

Step 1: Initialization

  • Create tracking arrays for rows, columns, and 2x2 blocks
  • Scan the board and mark existing numbers:
    • Row 0: has 1 and 4
    • Row 1: has 3
    • Row 2: has 3
    • Row 3: has 4 and 1
    • Similar for columns and blocks
  • Collect empty cells: [(0,1), (0,2), (1,0), (1,1), (1,3), (2,0), (2,2), (2,3), (3,1), (3,2)]

Step 2: Start DFS with first empty cell (0,1)

  • Try digit 1: Already in row 0 ❌
  • Try digit 2: Not in row 0, column 1, or top-left block ✓
    • Place 2 at (0,1)
    • Mark used: row[0][1]=True, col[1][1]=True, block[0][0][1]=True
    • Move to next empty cell

Step 3: Process cell (0,2)

  • Current board: [1, 2, ., 4]
  • Try digit 1: Already in row 0 ❌
  • Try digit 2: Already in row 0 ❌
  • Try digit 3: Not in row 0, column 2, or top-right block ✓
    • Place 3 at (0,2)
    • Continue to next cell

Step 4: Process cell (1,0)

  • Current board: [1, 2, 3, 4]
  • Try digit 1: Not used in row 1, column 0, or top-left block ❌ (but 1 is already in column 0 from row 0!)
  • Try digit 2: Valid ✓
    • Place 2 at (1,0)
    • Continue

Step 5: Process cell (1,1)

  • Current board shows conflict emerging
  • Try digit 1: Not in row 1, column 1, or top-left block ✓
  • Try digit 2: Already used in this block ❌
  • Try digit 3: Already in row 1 ❌
  • Try digit 4: Valid ✓
    • Place 4 at (1,1)
    • Continue

Step 6: Dead end at cell (1,3)

  • No valid digit works here (1 and 4 in column 3, 3 in row 1, 2 would violate block)
  • BACKTRACK: Return to previous cell (1,1)
  • Unmark 4: row[1][3]=False, etc.
  • No more digits to try at (1,1)
  • BACKTRACK further to (1,0)
  • Unmark 2 and continue trying other digits...

The process continues, systematically trying combinations and backtracking when stuck, until finding:

Final solution:
[1, 2, 3, 4]
[3, 4, 1, 2]
[2, 1, 4, 3]
[4, 3, 2, 1]

This example demonstrates:

  1. How we track used digits in rows, columns, and blocks
  2. The systematic trial of digits 1-9 (or 1-4 in this mini example)
  3. The backtracking mechanism when we hit dead ends
  4. How marking/unmarking maintains the state as we explore
  5. The recursive nature of trying each empty cell in sequence

For a full 9x9 Sudoku, the same process applies with digits 1-9 and 3x3 sub-boxes, just with more cells to fill and more backtracking branches to explore.

Solution Implementation

1class Solution:
2    def solveSudoku(self, board: List[List[str]]) -> None:
3        """
4        Solves a Sudoku puzzle using backtracking.
5        Modifies the board in-place.
6        """
7        def backtrack(index):
8            """
9            Recursively fill empty cells using DFS backtracking.
10          
11            Args:
12                index: Current index in the empty_cells list
13            """
14            nonlocal solved
15          
16            # Base case: all empty cells have been filled successfully
17            if index == len(empty_cells):
18                solved = True
19                return
20          
21            # Get the current empty cell's position
22            row_idx, col_idx = empty_cells[index]
23          
24            # Try each digit from 1 to 9
25            for digit in range(9):
26                # Check if placing digit+1 is valid in current position
27                if (not row_used[row_idx][digit] and 
28                    not col_used[col_idx][digit] and 
29                    not block_used[row_idx // 3][col_idx // 3][digit]):
30                  
31                    # Mark the digit as used in row, column, and 3x3 block
32                    row_used[row_idx][digit] = True
33                    col_used[col_idx][digit] = True
34                    block_used[row_idx // 3][col_idx // 3][digit] = True
35                  
36                    # Place the digit on the board
37                    board[row_idx][col_idx] = str(digit + 1)
38                  
39                    # Recursively solve for the next empty cell
40                    backtrack(index + 1)
41                  
42                    # If solution found, stop backtracking
43                    if solved:
44                        return
45                  
46                    # Backtrack: undo the current placement
47                    row_used[row_idx][digit] = False
48                    col_used[col_idx][digit] = False
49                    block_used[row_idx // 3][col_idx // 3][digit] = False
50      
51        # Initialize tracking arrays for used digits
52        # row_used[i][d] = True if digit d+1 is used in row i
53        row_used = [[False] * 9 for _ in range(9)]
54        # col_used[j][d] = True if digit d+1 is used in column j
55        col_used = [[False] * 9 for _ in range(9)]
56        # block_used[bi][bj][d] = True if digit d+1 is used in block (bi, bj)
57        block_used = [[[False] * 9 for _ in range(3)] for _ in range(3)]
58      
59        # List to store positions of empty cells
60        empty_cells = []
61      
62        # Flag to indicate if solution is found
63        solved = False
64      
65        # Initialize the board state and collect empty cells
66        for row_idx in range(9):
67            for col_idx in range(9):
68                if board[row_idx][col_idx] == '.':
69                    # Record empty cell position
70                    empty_cells.append((row_idx, col_idx))
71                else:
72                    # Mark existing digits as used
73                    digit = int(board[row_idx][col_idx]) - 1
74                    row_used[row_idx][digit] = True
75                    col_used[col_idx][digit] = True
76                    block_used[row_idx // 3][col_idx // 3][digit] = True
77      
78        # Start solving from the first empty cell
79        backtrack(0)
80
1class Solution {
2    // Flag to indicate if solution is found
3    private boolean solutionFound;
4  
5    // Reference to the sudoku board
6    private char[][] board;
7  
8    // List to store positions of empty cells (stored as single index: row * 9 + col)
9    private List<Integer> emptyCells = new ArrayList<>();
10  
11    // Track which numbers (0-8) are used in each row
12    private boolean[][] rowUsed = new boolean[9][9];
13  
14    // Track which numbers (0-8) are used in each column
15    private boolean[][] colUsed = new boolean[9][9];
16  
17    // Track which numbers (0-8) are used in each 3x3 block
18    private boolean[][][] blockUsed = new boolean[3][3][9];
19
20    public void solveSudoku(char[][] board) {
21        this.board = board;
22      
23        // Initialize tracking arrays and collect empty cell positions
24        for (int row = 0; row < 9; row++) {
25            for (int col = 0; col < 9; col++) {
26                if (board[row][col] == '.') {
27                    // Store empty cell position
28                    emptyCells.add(row * 9 + col);
29                } else {
30                    // Mark existing number as used in row, column, and block
31                    int digit = board[row][col] - '1';  // Convert '1'-'9' to 0-8
32                    rowUsed[row][digit] = true;
33                    colUsed[col][digit] = true;
34                    blockUsed[row / 3][col / 3][digit] = true;
35                }
36            }
37        }
38      
39        // Start backtracking from first empty cell
40        backtrack(0);
41    }
42
43    private void backtrack(int emptyCellIndex) {
44        // Base case: all empty cells have been filled successfully
45        if (emptyCellIndex == emptyCells.size()) {
46            solutionFound = true;
47            return;
48        }
49      
50        // Get row and column of current empty cell
51        int position = emptyCells.get(emptyCellIndex);
52        int row = position / 9;
53        int col = position % 9;
54      
55        // Try placing digits 1-9 (represented as 0-8 internally)
56        for (int digit = 0; digit < 9; digit++) {
57            // Check if digit can be placed at current position
58            if (!rowUsed[row][digit] && 
59                !colUsed[col][digit] && 
60                !blockUsed[row / 3][col / 3][digit]) {
61              
62                // Place the digit
63                rowUsed[row][digit] = true;
64                colUsed[col][digit] = true;
65                blockUsed[row / 3][col / 3][digit] = true;
66                board[row][col] = (char) (digit + '1');  // Convert 0-8 to '1'-'9'
67              
68                // Recursively fill next empty cell
69                backtrack(emptyCellIndex + 1);
70              
71                // Backtrack: remove the digit if solution not found
72                rowUsed[row][digit] = false;
73                colUsed[col][digit] = false;
74                blockUsed[row / 3][col / 3][digit] = false;
75            }
76          
77            // Early termination if solution is found
78            if (solutionFound) {
79                return;
80            }
81        }
82    }
83}
84
1class Solution {
2public:
3    void solveSudoku(vector<vector<char>>& board) {
4        // Track which numbers (0-8) are used in each row
5        bool rowUsed[9][9] = {false};
6        // Track which numbers (0-8) are used in each column
7        bool colUsed[9][9] = {false};
8        // Track which numbers (0-8) are used in each 3x3 block
9        bool blockUsed[3][3][9] = {false};
10      
11        // Flag to indicate when solution is found
12        bool solutionFound = false;
13      
14        // Store positions of all empty cells
15        vector<pair<int, int>> emptyCells;
16      
17        // Initialize the tracking arrays and collect empty cells
18        for (int row = 0; row < 9; ++row) {
19            for (int col = 0; col < 9; ++col) {
20                if (board[row][col] == '.') {
21                    // Add empty cell position to list
22                    emptyCells.push_back({row, col});
23                } else {
24                    // Mark existing number as used
25                    int digit = board[row][col] - '1';  // Convert char to 0-based index
26                    rowUsed[row][digit] = true;
27                    colUsed[col][digit] = true;
28                    blockUsed[row / 3][col / 3][digit] = true;
29                }
30            }
31        }
32      
33        // Recursive backtracking function to fill empty cells
34        function<void(int)> backtrack = [&](int cellIndex) {
35            // Base case: all empty cells have been filled
36            if (cellIndex == emptyCells.size()) {
37                solutionFound = true;
38                return;
39            }
40          
41            // Get current empty cell position
42            int currentRow = emptyCells[cellIndex].first;
43            int currentCol = emptyCells[cellIndex].second;
44            int blockRow = currentRow / 3;
45            int blockCol = currentCol / 3;
46          
47            // Try placing digits 1-9 (represented as 0-8 internally)
48            for (int digit = 0; digit < 9; ++digit) {
49                // Check if digit can be placed at current position
50                if (!rowUsed[currentRow][digit] && 
51                    !colUsed[currentCol][digit] && 
52                    !blockUsed[blockRow][blockCol][digit]) {
53                  
54                    // Place the digit
55                    rowUsed[currentRow][digit] = true;
56                    colUsed[currentCol][digit] = true;
57                    blockUsed[blockRow][blockCol][digit] = true;
58                    board[currentRow][currentCol] = digit + '1';  // Convert to char
59                  
60                    // Recursively fill next empty cell
61                    backtrack(cellIndex + 1);
62                  
63                    // If solution found, stop backtracking
64                    if (solutionFound) {
65                        return;
66                    }
67                  
68                    // Backtrack: remove the digit
69                    rowUsed[currentRow][digit] = false;
70                    colUsed[currentCol][digit] = false;
71                    blockUsed[blockRow][blockCol][digit] = false;
72                }
73            }
74        };
75      
76        // Start solving from the first empty cell
77        backtrack(0);
78    }
79};
80
1function solveSudoku(board: string[][]): void {
2    // Track which numbers (0-8) are used in each row
3    const rowUsed: boolean[][] = Array(9).fill(null).map(() => Array(9).fill(false));
4    // Track which numbers (0-8) are used in each column
5    const colUsed: boolean[][] = Array(9).fill(null).map(() => Array(9).fill(false));
6    // Track which numbers (0-8) are used in each 3x3 block
7    const blockUsed: boolean[][][] = Array(3).fill(null).map(() => 
8        Array(3).fill(null).map(() => Array(9).fill(false))
9    );
10  
11    // Flag to indicate when solution is found
12    let solutionFound = false;
13  
14    // Store positions of all empty cells
15    const emptyCells: [number, number][] = [];
16  
17    // Initialize the tracking arrays and collect empty cells
18    for (let row = 0; row < 9; row++) {
19        for (let col = 0; col < 9; col++) {
20            if (board[row][col] === '.') {
21                // Add empty cell position to list
22                emptyCells.push([row, col]);
23            } else {
24                // Mark existing number as used
25                const digit = parseInt(board[row][col]) - 1; // Convert char to 0-based index
26                rowUsed[row][digit] = true;
27                colUsed[col][digit] = true;
28                blockUsed[Math.floor(row / 3)][Math.floor(col / 3)][digit] = true;
29            }
30        }
31    }
32  
33    // Recursive backtracking function to fill empty cells
34    const backtrack = (cellIndex: number): void => {
35        // Base case: all empty cells have been filled
36        if (cellIndex === emptyCells.length) {
37            solutionFound = true;
38            return;
39        }
40      
41        // Get current empty cell position
42        const currentRow = emptyCells[cellIndex][0];
43        const currentCol = emptyCells[cellIndex][1];
44        const blockRow = Math.floor(currentRow / 3);
45        const blockCol = Math.floor(currentCol / 3);
46      
47        // Try placing digits 1-9 (represented as 0-8 internally)
48        for (let digit = 0; digit < 9; digit++) {
49            // Check if digit can be placed at current position
50            if (!rowUsed[currentRow][digit] && 
51                !colUsed[currentCol][digit] && 
52                !blockUsed[blockRow][blockCol][digit]) {
53              
54                // Place the digit
55                rowUsed[currentRow][digit] = true;
56                colUsed[currentCol][digit] = true;
57                blockUsed[blockRow][blockCol][digit] = true;
58                board[currentRow][currentCol] = String(digit + 1); // Convert to string
59              
60                // Recursively fill next empty cell
61                backtrack(cellIndex + 1);
62              
63                // If solution found, stop backtracking
64                if (solutionFound) {
65                    return;
66                }
67              
68                // Backtrack: remove the digit
69                rowUsed[currentRow][digit] = false;
70                colUsed[currentCol][digit] = false;
71                blockUsed[blockRow][blockCol][digit] = false;
72            }
73        }
74    };
75  
76    // Start solving from the first empty cell
77    backtrack(0);
78}
79

Time and Space Complexity

Time Complexity: O(9^m) where m is the number of empty cells, which in the worst case can be up to 81, giving us O(9^81).

The algorithm uses backtracking to fill empty cells in the Sudoku board. For each empty cell stored in list t, we try all 9 possible values (1-9). In the worst case scenario where the board is completely empty, we have 81 empty cells. For each cell, we potentially explore 9 branches, and this branching continues for all empty cells. Although pruning occurs when a number violates Sudoku rules (checking row[i][v], col[j][v], and block[i//3][j//3][v]), the theoretical worst case still requires exploring up to 9^81 possibilities.

Space Complexity: O(9^2) or O(81).

The space complexity consists of:

  • Three 2D/3D boolean arrays for tracking used numbers:
    • row: 9 × 9 = 81 space
    • col: 9 × 9 = 81 space
    • block: 3 × 3 × 9 = 81 space
  • List t storing coordinates of empty cells: at most 81 pairs
  • Recursion stack depth: at most m (number of empty cells), which is at most 81

The total auxiliary space used is O(81 + 81 + 81 + 81 + 81) = O(405) = O(9^2), which simplifies to O(9^2) or O(81) since these are constants.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Backtrack Properly

One of the most common mistakes is not correctly undoing the state changes when backtracking. If you mark a digit as used but forget to unmark it when the path doesn't lead to a solution, the algorithm will incorrectly think that digit is permanently unavailable for that position.

Incorrect Implementation:

# Missing backtrack cleanup
if valid:
    row_used[row_idx][digit] = True
    col_used[col_idx][digit] = True
    block_used[row_idx // 3][col_idx // 3][digit] = True
    board[row_idx][col_idx] = str(digit + 1)
    backtrack(index + 1)
    # Forgot to reset the states here!

Correct Implementation:

if valid:
    # Mark as used
    row_used[row_idx][digit] = True
    col_used[col_idx][digit] = True
    block_used[row_idx // 3][col_idx // 3][digit] = True
    board[row_idx][col_idx] = str(digit + 1)
  
    backtrack(index + 1)
  
    # Only backtrack if solution not found
    if not solved:
        row_used[row_idx][digit] = False
        col_used[col_idx][digit] = False
        block_used[row_idx // 3][col_idx // 3][digit] = False
        # Note: We don't need to reset board[row_idx][col_idx] since it will be overwritten

2. Incorrect Block Index Calculation

Many people struggle with correctly mapping a cell (i, j) to its 3x3 block. Using the wrong formula will cause the algorithm to check the wrong block constraints.

Common Mistakes:

# Wrong: Using modulo instead of integer division
block_used[row_idx % 3][col_idx % 3][digit]

# Wrong: Not dividing both indices
block_used[row_idx // 3][col_idx][digit]

# Wrong: Using single index for blocks
block_used[(row_idx // 3) * 3 + (col_idx // 3)][digit]

Correct Formula:

# Correct: Both indices divided by 3
block_used[row_idx // 3][col_idx // 3][digit]

3. Off-by-One Errors with Digit Indexing

Since Sudoku uses digits 1-9 but array indices start at 0, it's easy to mix up the conversions between digit values and array indices.

Common Mistake:

# Wrong: Using digit directly as index
digit = int(board[i][j])  # This is 1-9
row_used[i][digit] = True  # Index out of bounds when digit = 9

# Wrong: Forgetting to add 1 when placing on board
board[row_idx][col_idx] = str(digit)  # Places 0-8 instead of 1-9

Correct Approach:

# Reading from board: subtract 1 to get index
digit_index = int(board[i][j]) - 1  # Convert 1-9 to 0-8

# Writing to board: add 1 to get actual digit
board[row_idx][col_idx] = str(digit_index + 1)  # Convert 0-8 to 1-9

4. Not Stopping After Finding the First Solution

Without proper termination, the backtracking might continue searching even after finding a valid solution, potentially overwriting it or wasting computation time.

Problematic Pattern:

def backtrack(index):
    if index == len(empty_cells):
        # Found solution but continue searching
        return True  # Return value gets ignored
  
    for digit in range(9):
        if valid:
            # ... make changes ...
            backtrack(index + 1)  # Doesn't check return value
            # ... undo changes ...

Proper Solution:

def backtrack(index):
    nonlocal solved
  
    if index == len(empty_cells):
        solved = True  # Set flag
        return
  
    for digit in range(9):
        if valid:
            # ... make changes ...
            backtrack(index + 1)
          
            if solved:  # Check flag immediately
                return  # Stop searching
          
            # ... undo changes ...

5. Modifying the Board During Initialization

Some implementations try to be clever by filling obvious cells during initialization, but this can lead to inconsistencies if not done carefully with the tracking arrays.

Better Approach: Keep initialization simple - just record the initial state and let the backtracking algorithm handle all the filling logic uniformly.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More