BackTracking

18 problems · 20 approaches

Rat in a maze — print all pathsMedium1 approach

Problem

A rat starts at (0, 0) of an n × n grid (1 = open, 0 = blocked) and must reach (n−1, n−1). It moves U, D, L or R and cannot revisit a cell. Return every path as a string of moves, in lexicographic order.

Example 1

Input:  [[1,0,0,0], [1,1,0,1], [1,1,0,0], [0,1,1,1]]
Output: ["DDRDRR", "DRDDRR"]

Also asked as: Rat in a maze Problem · Find shortest safe route in a path with landmines

1. DFS with a visited mark, path string, undo on return

Time O(4^(R·C))Space O(R·C)

From (0,0) try moves D,L,R,U (order defines lexical output); mark the cell, recurse, unmark. Record the path when you reach the target.

function ratMaze(grid) {
const n = grid.length, res = [];
const vis = Array.from({ length: n }, () => Array(n).fill(false));
const dirs = [[1, 0, 'D'], [0, -1, 'L'], [0, 1, 'R'], [-1, 0, 'U']];
const bt = (r, c, path) => {
if (r === n - 1 && c === n - 1) { res.push(path); return; }
for (const [dr, dc, ch] of dirs) {
const nr = r + dr, nc = c + dc;
if (nr >= 0 && nc >= 0 && nr < n && nc < n && !vis[nr][nc] && grid[nr][nc] === 1) {
vis[nr][nc] = true;
bt(nr, nc, path + ch);
vis[nr][nc] = false;
}
}
};
if (grid[0][0] === 1) { vis[0][0] = true; bt(0, 0, ''); }
return res;
}

Shortest safe route with landmines: mark every cell adjacent to a mine as unsafe first, then either backtrack tracking the best length, or BFS for the true shortest.

N-Queens — all solutionsHard1 approach

Problem

Place n queens on an n × n board so that no two attack each other (same row, column or diagonal). Return every distinct board.

Example 1

Input:  n = 4
Output: [".Q..","...Q","Q...","..Q."] and ["..Q.","Q...","...Q",".Q.."]

Also asked as: Printing all solutions in N-Queen Problem

1. Row-by-row with column & diagonal sets

Time O(N!)Space O(N)

Place one queen per row. A column is free if it is not in `cols`, and diagonals are tracked by r−c and r+c. Recurse, backtrack.

function solveNQueens(n) {
const res = [], pos = [];
const cols = new Set(), d1 = new Set(), d2 = new Set();
const bt = (r) => {
if (r === n) { res.push(pos.map((c) => '.'.repeat(c) + 'Q' + '.'.repeat(n - c - 1))); return; }
for (let c = 0; c < n; c++) {
if (cols.has(c) || d1.has(r - c) || d2.has(r + c)) continue;
cols.add(c); d1.add(r - c); d2.add(r + c); pos.push(c);
bt(r + 1);
cols.delete(c); d1.delete(r - c); d2.delete(r + c); pos.pop();
}
};
bt(0);
return res;
}
Sudoku solverHard1 approach

Problem

Fill the empty cells (".") of a 9 × 9 Sudoku so that each row, each column and each 3 × 3 box contains the digits 1–9 exactly once. The puzzle has exactly one solution.

Example 1

Input:  a partially filled 9 × 9 board
Output: the completed board

Also asked as: Sudoku Solver

1. Fill the first empty cell, try 1–9, recurse

Time exponential (fast in practice)Space O(1)

For each empty cell try every digit that is valid in its row, column and 3×3 box; recurse; undo if the branch fails.

function solveSudoku(board) {
const valid = (r, c, ch) => {
for (let i = 0; i < 9; i++) {
if (board[r][i] === ch || board[i][c] === ch) return false;
const br = 3 * ((r / 3) | 0) + ((i / 3) | 0);
const bc = 3 * ((c / 3) | 0) + (i % 3);
if (board[br][bc] === ch) return false;
}
return true;
};
const bt = () => {
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 (valid(r, c, ch)) {
board[r][c] = ch;
if (bt()) return true;
board[r][c] = '.';
}
}
return false;
}
return true;
};
bt();
return board;
}
Remove minimum invalid parenthesesHard1 approach

Problem

Remove the minimum number of parentheses to make the string valid, and return every distinct valid result. The string may also contain letters.

Example 1

Input:  "()())()"
Output: ["(())()", "()()()"]

Example 2

Input:  "(a)())()"
Output: ["(a())()", "(a)()()"]

Also asked as: Remove Invalid Parentheses

1. BFS by removal count

Time O(2ⁿ) worstSpace O(2ⁿ)

Level 0 = the original string; each level removes one character. Return all valid strings from the first level that contains any — that is the minimum number of removals.

function removeInvalidParentheses(s) {
const isValid = (str) => {
let bal = 0;
for (const c of str) {
if (c === '(') bal++;
else if (c === ')' && --bal < 0) return false;
}
return bal === 0;
};
let level = new Set([s]);
while (level.size) {
const valid = [...level].filter(isValid);
if (valid.length) return valid;
const next = new Set();
for (const str of level)
for (let i = 0; i < str.length; i++)
if (str[i] === '(' || str[i] === ')') next.add(str.slice(0, i) + str.slice(i + 1));
level = next;
}
return [''];
}
Subset sum — does any subset add up to the target?Medium1 approach

Problem

Given non-negative integers and a target sum, return true if some subset of them adds up exactly to the target.

Example 1

Input:  arr = [3, 34, 4, 12, 5, 2], sum = 9
Output: true

4 + 5.

Example 2

Input:  same, sum = 30
Output: false

Also asked as: Subset Sum Problem · Partition of a set intoK subsets with equal sum · Tug of War

1. Include / exclude with pruning

Time O(2ⁿ)Space O(n)

At each index either take the number (if it fits) or skip it. Prune when the remaining sum cannot reach the target.

function subsetSum(nums, target) {
const bt = (i, remaining) => {
if (remaining === 0) return true;
if (i === nums.length || remaining < 0) return false;
return bt(i + 1, remaining - nums[i]) || bt(i + 1, remaining);
};
return bt(0, target);
}

Partition into k equal-sum subsets: check total % k === 0, then try to fill k buckets to total/k with backtracking (sort desc, skip duplicate bucket states). Tug of War: split into two halves of size n/2 minimising the sum difference.

The Knight's tourHard1 approach

Problem

Move a knight around an n × n board so that it visits every square exactly once. Return the board with each square numbered by its visiting order (the backtracking version is usually shown with n = 8).

Example 1

Input:  n = 5, start (0, 0)
Output: a 5 × 5 grid numbered 0..24 in knight-move order

Also asked as: The Knight’s tour problem

1. DFS numbering squares; backtrack on dead ends

Time exponential (Warnsdorff heuristic makes it fast)Space O(N²)

From the current square, try all 8 knight moves to an unvisited in-bounds square; mark it with the move number; recurse; unmark on failure.

function knightTour(N) {
const board = Array.from({ length: N }, () => Array(N).fill(-1));
const moves = [[2,1],[1,2],[-1,2],[-2,1],[-2,-1],[-1,-2],[1,-2],[2,-1]];
board[0][0] = 0;
const bt = (r, c, step) => {
if (step === N * N) return true;
for (const [dr, dc] of moves) {
const nr = r + dr, nc = c + dc;
if (nr >= 0 && nc >= 0 && nr < N && nc < N && board[nr][nc] === -1) {
board[nr][nc] = step;
if (bt(nr, nc, step + 1)) return true;
board[nr][nc] = -1;
}
}
return false;
};
return bt(0, 0, 1) ? board : null;
}
Combination SumMedium1 approach

Problem

Given distinct positive candidates and a target, return every unique combination of candidates that sums to target. Each candidate may be used any number of times, and combinations that differ only in order count once.

Example 1

Input:  candidates = [2, 3, 6, 7], target = 7
Output: [[2, 2, 3], [7]]

Also asked as: Combinational Sum

1. Backttrack, allowing reuse (recurse on the same index)

Time O(2^target)Space O(target / min)

From `start`, try each candidate; subtract it from the target and recurse on `i` (reuse allowed) or `i+1` (no reuse). Sort to prune once a candidate exceeds the remaining target.

function combinationSum(candidates, target) {
candidates.sort((a, b) => a - b);
const res = [], path = [];
const bt = (start, remaining) => {
if (remaining === 0) { res.push([...path]); return; }
for (let i = start; i < candidates.length && candidates[i] <= remaining; i++) {
path.push(candidates[i]);
bt(i, remaining - candidates[i]); // i (not i+1) ⇒ reuse allowed
path.pop();
}
};
bt(0, target);
return res;
}
Maximum number by doing at most K swapsMedium1 approach

Problem

Given a number as a string, swap any two digits at most k times to make the largest possible number. Return it.

Example 1

Input:  s = "1234567", k = 4
Output: "7654321"

Example 2

Input:  s = "3435335", k = 3
Output: "5543333"

Also asked as: Find Maximum number possible by doing at-most K swaps

1. Backtracking — at each position bring the largest reachable digit forward

Time O(n^k) worstSpace O(n)

For each position, find the max digit to its right; try swapping it in (spending a swap) and recurse. Track the best string seen. Only swap when it actually increases the number.

function maxNumberKSwaps(numStr, k) {
const digits = [...numStr];
let best = numStr;
const bt = (idx, swaps) => {
if (swaps === 0 || idx === digits.length) return;
const maxDigit = Math.max(...digits.slice(idx).map(Number));
for (let j = digits.length - 1; j > idx; j--) {
if (Number(digits[j]) === maxDigit && digits[j] !== digits[idx]) {
[digits[idx], digits[j]] = [digits[j], digits[idx]];
const cur = digits.join('');
if (cur > best) best = cur;
bt(idx + 1, swaps - 1);
[digits[idx], digits[j]] = [digits[j], digits[idx]];
}
}
bt(idx + 1, swaps); // also allow not swapping at this position
};
bt(0, k);
return best;
}
Longest route in a matrix with hurdles / all paths top-left to bottom-rightMedium1 approach

Problem

(1) Longest route: in a grid of 1 (open) and 0 (hurdle), return the length of the longest path from source to destination that never revisits a cell, or −1 if there is none. (2) All paths: print every path from the top-left to the bottom-right corner of an m × n grid, moving only right or down.

Example 1

Input:  all paths in [[1,2,3], [4,5,6]]
Output: 1 4 5 6, 1 2 5 6, 1 2 3 6

Also asked as: Longest Possible Route in a Matrix with Hurdles · Print all possible paths from top left to bottom right of a mXn matrix

1. DFS with a visited grid, maximise / collect on reaching the target

Time O(4^(R·C))Space O(R·C)

From each cell recurse into the 4 open, unvisited, in-bounds neighbours; mark on entry, unmark on exit. Track the longest length (or push the path) when you hit the destination.

function longestRoute(grid, sr, sc, dr, dc) {
const R = grid.length, C = grid[0].length;
const vis = Array.from({ length: R }, () => Array(C).fill(false));
let best = -1;
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
const dfs = (r, c, len) => {
if (r === dr && c === dc) { best = Math.max(best, len); return; }
for (const [ddr, ddc] of dirs) {
const nr = r + ddr, nc = c + ddc;
if (nr >= 0 && nc >= 0 && nr < R && nc < C && grid[nr][nc] === 1 && !vis[nr][nc]) {
vis[nr][nc] = true;
dfs(nr, nc, len + 1);
vis[nr][nc] = false;
}
}
};
vis[sr][sc] = true;
dfs(sr, sc, 0);
return best;
}

All paths (only moving right/down) is simpler: recurse pushing "R"/"D" until you reach (m−1, n−1).

Kth permutation sequence of 1..NMedium1 approach

Problem

The n! permutations of [1..n] are listed in lexicographic order. Return the kth one (1-indexed) without generating the others.

Example 1

Input:  n = 3, k = 3
Output: "213"

123, 132, 213, …

Example 2

Input:  n = 4, k = 9
Output: "2314"

Also asked as: Find the K-th Permutation Sequence of first N natural numbers

1. Factorial number system (no enumeration)

Time O(n²)Space O(n)

The first digit is determined by k / (n−1)!; remove it, take k mod (n−1)!, repeat. O(n²) without generating permutations.

function getPermutation(n, k) {
const fact = [1];
for (let i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
const digits = Array.from({ length: n }, (_, i) => i + 1);
k--; // 0-indexed
let res = '';
for (let i = n; i >= 1; i--) {
const idx = Math.floor(k / fact[i - 1]);
res += digits[idx];
digits.splice(idx, 1);
k %= fact[i - 1];
}
return res;
}
Subsets IIMedium1 approach

Problem

The array may contain duplicates. Return every possible subset, with no duplicate subsets in the result.

Example 1

Input:  [1, 2, 2]
Output: [[], [1], [1,2], [1,2,2], [2], [2,2]]

1. Sort + skip equal siblings

Time O(n · 2^n)Space O(n)

Sort so duplicates are adjacent. At one recursion level, use a repeated value only for its first occurrence (i > start && nums[i] === nums[i − 1] → skip).

function subsetsWithDup(nums) {
nums.sort((a, b) => a - b);
const res = [], path = [];
const bt = (start) => {
res.push([...path]);
for (let i = start; i < nums.length; i++) {
if (i > start && nums[i] === nums[i - 1]) continue;
path.push(nums[i]);
bt(i + 1);
path.pop();
}
};
bt(0);
return res;
}
Combination Sum IIMedium1 approach

Problem

Return every unique combination of candidates that sums to target. Each candidate may be used at most once, and the candidates may contain duplicates, but the result must not contain duplicate combinations.

Example 1

Input:  candidates = [10, 1, 2, 7, 6, 1, 5], target = 8
Output: [[1,1,6], [1,2,5], [1,7], [2,6]]

1. Sort + skip duplicates + prune

Time O(2^n) worst caseSpace O(n)

Each number used at most once (recurse on i + 1). Skip equal siblings like Subsets II, and stop the loop once a candidate exceeds the remaining target.

function combinationSum2(cands, target) {
cands.sort((a, b) => a - b);
const res = [], path = [];
const bt = (start, rem) => {
if (rem === 0) { res.push([...path]); return; }
for (let i = start; i < cands.length && cands[i] <= rem; i++) {
if (i > start && cands[i] === cands[i - 1]) continue;
path.push(cands[i]);
bt(i + 1, rem - cands[i]);
path.pop();
}
};
bt(0, target);
return res;
}
Letter Combinations of a Phone NumberMedium1 approach

Problem

Given a string of digits 2–9, return every letter combination they could represent on a phone keypad (2 = abc, 3 = def, …, 9 = wxyz).

Example 1

Input:  "23"
Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

1. Backtracking over digits

Time O(4^n · n)Space O(n)

One recursion level per digit, one branch per letter on that key.

function letterCombinations(digits) {
if (!digits) return [];
const map = ['', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
const res = [];
const bt = (i, cur) => {
if (i === digits.length) { res.push(cur); return; }
for (const ch of map[digits[i]]) bt(i + 1, cur + ch);
};
bt(0, '');
return res;
}
Generate ParenthesesMedium1 approach

Problem

Return every well-formed string made of n pairs of parentheses.

Example 1

Input:  n = 3
Output: ["((()))", "(()())", "(())()", "()(())", "()()()"]

1. Backtracking with open/close counts

Time O(4^n / √n) — the nth Catalan numberSpace O(n)

Add "(" while fewer than n are open; add ")" only while it would not exceed the opens. Every leaf is valid — no filtering needed.

function generateParenthesis(n) {
const res = [];
const bt = (cur, open, close) => {
if (cur.length === 2 * n) { res.push(cur); return; }
if (open < n) bt(cur + '(', open + 1, close);
if (close < open) bt(cur + ')', open, close + 1);
};
bt('', 0, 0);
return res;
}