Docs
/
DSA Advanced
Chapter 10
10 — Backtracking
What is Backtracking?
Build solutions incrementally, abandon (prune) paths that can't lead to valid solutions.
Explore → Check constraints → Valid? → Continue / Backtrack
[]
/ | \
1 2 3
/ \ |
1,2 1,3 2,3
|
1,2,3
Template
function backtrack(result: any[], current: any[], choices: any[], ...constraints) {
if (isComplete(current)) {
result.push([...current]); // Found valid solution
return;
}
for (const choice of choices) {
if (!isValid(choice, current)) continue; // Prune
current.push(choice); // Choose
backtrack(result, current, ...); // Explore
current.pop(); // Unchoose (backtrack)
}
}
Permutations
function permute(nums: number[]): number[][] {
const result: number[][] = [];
const used = new Array(nums.length).fill(false);
function backtrack(current: number[]) {
if (current.length === nums.length) {
result.push([...current]);
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue;
used[i] = true;
current.push(nums[i]);
backtrack(current);
current.pop();
used[i] = false;
}
}
backtrack([]);
return result;
}
// permute([1,2,3]) → [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Subsets
function subsets(nums: number[]): number[][] {
const result: number[][] = [];
function backtrack(start: number, current: number[]) {
result.push([...current]);
for (let i = start; i < nums.length; i++) {
current.push(nums[i]);
backtrack(i + 1, current);
current.pop();
}
}
backtrack(0, []);
return result;
}
// subsets([1,2,3]) → [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]
Combination Sum
// Find combinations that sum to target (can reuse elements)
function combinationSum(candidates: number[], target: number): number[][] {
const result: number[][] = [];
function backtrack(start: number, current: number[], remaining: number) {
if (remaining === 0) { result.push([...current]); return; }
if (remaining < 0) return;
for (let i = start; i < candidates.length; i++) {
current.push(candidates[i]);
backtrack(i, current, remaining - candidates[i]); // i, not i+1 (reuse allowed)
current.pop();
}
}
backtrack(0, [], target);
return result;
}
N-Queens
function solveNQueens(n: number): string[][] {
const result: string[][] = [];
const cols = new Set<number>();
const diag1 = new Set<number>(); // row - col
const diag2 = new Set<number>(); // row + col
const board = Array.from({ length: n }, () => '.'.repeat(n));
function backtrack(row: number) {
if (row === n) {
result.push([...board]);
return;
}
for (let col = 0; col < n; col++) {
if (cols.has(col) || diag1.has(row - col) || diag2.has(row + col)) continue;
cols.add(col); diag1.add(row - col); diag2.add(row + col);
board[row] = '.'.repeat(col) + 'Q' + '.'.repeat(n - col - 1);
backtrack(row + 1);
cols.delete(col); diag1.delete(row - col); diag2.delete(row + col);
board[row] = '.'.repeat(n);
}
}
backtrack(0);
return result;
}
Sudoku Solver
function solveSudoku(board: string[][]): void {
function isValid(row: number, col: number, ch: string): boolean {
const boxRow = 3 * Math.floor(row / 3);
const boxCol = 3 * Math.floor(col / 3);
for (let i = 0; i < 9; i++) {
if (board[row][i] === ch) return false;
if (board[i][col] === ch) return false;
if (board[boxRow + Math.floor(i / 3)][boxCol + (i % 3)] === ch) return false;
}
return true;
}
function solve(): boolean {
for (let r = 0; r < 9; r++) {
for (let c = 0; c < 9; c++) {
if (board[r][c] !== '.') continue;
for (let d = 1; d <= 9; d++) {
const ch = String(d);
if (!isValid(r, c, ch)) continue;
board[r][c] = ch;
if (solve()) return true;
board[r][c] = '.'; // Backtrack
}
return false; // No valid digit → backtrack
}
}
return true; // All cells filled
}
solve();
}
Pruning Strategies
1. Skip duplicates (sorted array):
if (i > start && nums[i] === nums[i-1]) continue;
2. Early termination:
if (currentSum > target) return; // Can't improve
3. Constraint checking:
if (!isValid(choice)) continue; // Don't explore invalid paths
4. Sorting first:
Sort to enable duplicate skipping and early termination
Key Takeaways
- Backtracking = try → check → continue or undo
- Permutations: use
used[]array; Subsets/Combinations: usestartindex - Pruning is critical — skip invalid paths early to avoid exponential blowup
- Use Sets for O(1) constraint checking (columns, diagonals)
- Always
[...current]when pushing to results (avoid reference issues) - Time complexity is typically exponential — but pruning makes it practical