Sudoku Solver Using Backtracking in DSA Typescript - Time & Space Complexity
We want to understand how the time needed to solve a Sudoku puzzle changes as the puzzle size or difficulty changes.
Specifically, how the backtracking method explores possibilities and how that affects the time it takes.
Analyze the time complexity of the following Sudoku solver using backtracking.
function solveSudoku(board: number[][]): boolean {
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (board[row][col] === 0) {
for (let num = 1; num <= 9; num++) {
if (isValid(board, row, col, num)) {
board[row][col] = num;
if (solveSudoku(board)) return true;
board[row][col] = 0;
}
}
return false;
}
}
}
return true;
}
This code tries numbers 1 to 9 in empty cells and uses recursion to fill the board step-by-step.
Look at what repeats most in this code.
- Primary operation: Trying numbers 1 to 9 in each empty cell and checking if the number fits.
- How many times: Potentially for every empty cell, and for each number, the function calls itself recursively to try the next cell.
Imagine the puzzle has n empty cells.
| Input Size (empty cells n) | Approx. Operations |
|---|---|
| 10 | Up to 9^10 tries (about 3.5 billion) |
| 20 | Up to 9^20 tries (very large, trillions) |
| 40 | Up to 9^40 tries (astronomically large) |
Pattern observation: The number of tries grows exponentially as the number of empty cells increases.
Time Complexity: O(9^n)
This means the time needed can grow very fast, roughly nine times more work for each extra empty cell.
[X] Wrong: "The solver always tries all 9 numbers for every empty cell."
[OK] Correct: The solver stops trying numbers as soon as it finds a valid one that leads to a solution, so it often tries fewer than 9 numbers per cell.
Understanding this exponential growth helps you explain why backtracking can be slow and why pruning or smarter checks matter.
It shows your grasp of how recursive search explores many possibilities and how complexity grows with input size.
"What if we used a heuristic to pick the next empty cell with the fewest valid options? How would the time complexity change?"
Practice
Solution
Step 1: Understand backtracking approach
Backtracking tries numbers in empty cells one by one and checks if they fit the Sudoku rules.Step 2: Handle conflicts by undoing choices
If a number causes a conflict later, backtracking removes it and tries the next number.Final Answer:
Try placing numbers one by one and backtrack if a conflict occurs -> Option BQuick Check:
Backtracking = Try and undo choices [OK]
- Thinking backtracking fills cells randomly
- Assuming sorting solves Sudoku
- Replacing zeros without checks
Solution
Step 1: Analyze function parameters
The function needs the board (2D array), row, column, and number to check validity.Step 2: Check return type
It should return a boolean indicating if placing the number is valid or not.Final Answer:
function isValid(board: number[][], row: number, col: number, num: number): boolean -> Option AQuick Check:
Correct parameters and boolean return [OK]
- Using wrong parameter types
- Missing row or column parameters
- Returning wrong data type
solveSudoku(board), what will be the value at board[0][2] after the first successful placement?const 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] ];
Solution
Step 1: Identify empty cell and possible numbers
At position (0,2), possible numbers are those not in row 0, column 2, or 3x3 box. Candidates: 1, 2, 4.Step 2: Check numbers 1 to 9 for validity
1, 2, 4 are initially valid. Backtracking tries 1 first, recurses but backtracks due to later conflicts; tries 2, same; 4 succeeds and solves the puzzle.Final Answer:
4 -> Option AQuick Check:
First successful number placed = 4 [OK]
- Choosing a number already in the same column
- Ignoring 3x3 box constraints
- Assuming first empty cell is filled with 1 always
function isValid(board: number[][], row: number, col: number, num: number): boolean {
for (let i = 0; i < 9; i++) {
if (board[row][i] === num) return false;
if (board[i][col] === num) return false;
}
const startRow = row - row % 3;
const startCol = col - col % 3;
for (let i = startRow; i < startRow + 3; i++) {
for (let j = startCol; j < startCol + 3; j++) {
if (board[i][j] === num) return false;
}
}
return true;
}Solution
Step 1: Review row and column checks
The loop checks all cells in the given row and column for the number.Step 2: Review 3x3 box check
startRow and startCol correctly find the top-left cell of the 3x3 box; nested loops check all 9 cells.Final Answer:
No bug; the function correctly checks validity -> Option DQuick Check:
All constraints checked properly [OK]
- Miscomputing startRow or startCol
- Forgetting to check 3x3 box
- Returning false too early
Solution
Step 1: Understand base case in backtracking
The base case returns true when all cells are filled correctly, stopping recursion.Step 2: Modify to return true immediately on last cell
Returning true immediately when the last empty cell is filled avoids unnecessary further checks.Final Answer:
Return true immediately when a valid number is placed in the last empty cell -> Option CQuick Check:
Stop recursion early by returning true [OK]
- Ignoring base case and continuing recursion
- Skipping box checks causing wrong solutions
- Trying all numbers even after solution
Ready to prove it?
Pass the test and this pattern moves unknown
