Backtracking
Decision Tree | Pruning | Subsets | Permutations | N-Queens
1 β What Is Backtracking?
Backtracking is a systematic method for exploring all possible solutions to a problem by building candidates incrementally and abandoning (pruning) a candidate as soon as it is determined that it cannot lead to a valid solution. It is a refined form of brute-force that avoids redundant exploration.
The Backtracking Template
Every backtracking solution follows the same skeleton:
- CHOOSE: pick a candidate element to add to the current partial solution.
- EXPLORE: recurse with the candidate added.
- UN-CHOOSE (backtrack): undo the choice before trying the next candidate.
The recursion tree is called the 'decision tree'. Each node is a partial state; each edge is a choice. Leaves are complete solutions or dead ends. Pruning cuts entire subtrees early: if no solution can exist in this subtree (constraint violated), skip it without exploring.
// Try every candidate for the next choice
for (auto& candidate : getCandidates(state)) {
if (!isValid(state, candidate)) continue; // prune invalid
makeChoice(state, candidate); // CHOOSE
backtrack(state, results); // EXPLORE
undoChoice(state, candidate); // UN-CHOOSE
}
}
</div>
<h3>1.1 β Backtracking vs Brute Force vs DP</h3>
<div class="table-responsive">
<table class="insight-table">
<thead>
<tr>
<th>Dimension</th>
<th>Brute Force</th>
<th>Backtracking</th>
<th>Dynamic Programming</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Exploration</strong></td>
<td>All possible states</td>
<td>Prune invalid subtrees early</td>
<td>Reuse stored subproblem results</td>
</tr>
<tr>
<td><strong>When used</strong></td>
<td>No known structure</td>
<td>Constraint-based search</td>
<td>Overlapping subproblems</td>
</tr>
<tr>
<td><strong>Undo step</strong></td>
<td>Not needed</td>
<td>Required (un-choose)</td>
<td>Not needed</td>
</tr>
<tr>
<td><strong>Complexity</strong></td>
<td>Worst case exponential</td>
<td>Exponential but pruned</td>
<td>Polynomial (with memoisation)</td>
</tr>
<tr>
<td><strong>Output type</strong></td>
<td>One optimal value</td>
<td>All valid solutions (or one)</td>
<td>One optimal value</td>
</tr>
<tr>
<td><strong>Space</strong></td>
<td>O(1) extra</td>
<td>O(depth) recursion stack</td>
<td>O(n) to O(nΒ²) table</td>
</tr>
<tr>
<td><strong>Classic problems</strong></td>
<td>Loop over all subsets</td>
<td>Subsets, Permutations, N-Queens</td>
<td>Coin Change, LCS, Knapsack</td>
</tr>
</tbody>
</table>
</div>
<div class="insight-box idea">
<h4>Real-World Analogy: Solving a Maze</h4>
<p>You stand at the entrance of a maze and want to find the exit. At each junction, you try one path. If you hit a dead end, you backtrack to the last junction and try a different path. This is exactly backtracking: explore a path fully, and if it fails, undo your steps and try the next option.<br><br><strong>Pruning:</strong> if a corridor is blocked (invalid constraint), skip it immediately without entering.<br><strong>The maze metaphor maps to code:</strong> junction = recursive call, dead end = base case failure, backtrack = undo the last choice.</p>
</div>
</section>
<!-- SECTION 2 -->
<section id="section-2" class="chapter-section">
<h2> 2 β Visual Diagrams: Decision Trees</h2>
<h3>Diagram 1 β Subsets Decision Tree</h3>
<p><strong>Subsets: Full Decision Tree (n=3)</strong><br><code>nums = [1, 2, 3]</code><br>Generate all subsets (power set). At each level, we decide: include <code>nums[i]</code> or skip it.</p>
<pre class="trace-output">
[]
/ \
[1] []
/ \ / \
[1,2] [1] [2] []
/ \ / \ / \ / \
[1,2,3][1,2][1,3][1][2,3][2][3][]
</pre>
<p>Leaves (all 8 = 2Β³ subsets): <code>[1,2,3] [1,2] [1,3] [1] [2,3] [2] [3] []</code><br>No pruning needed here (all paths are valid). Total nodes in tree = 2^(n+1) - 1 = 15 for n=3.</p>
<h3>Diagram 2 β Permutations Decision Tree</h3>
<p><strong>Permutations: Decision Tree (n=3)</strong><br><code>nums = [1, 2, 3]</code><br>Generate all permutations. At each level, pick one unused number. <code>used = {}</code> tracks which numbers are already in the current path.</p>
<pre class="trace-output">
[]
/ | \
[1] [2] [3]
/ \ / \ / \
[1,2][1,3][2,1][2,3][3,1][3,2]
| | | | | |
[1,2,3][1,3,2][2,1,3][2,3,1][3,1,2][3,2,1]
</pre>
<p>6 leaves = 3! = n! permutations. No pruning (no duplicates in input). Total nodes = 1 + 3 + 6 + 6 = 16 for n=3.<br><em>With duplicates (e.g. [1,1,2]):</em> sort first, then skip if <code>nums[i] == nums[i-1]</code> and <code>nums[i-1]</code> was <strong>NOT</strong> used in this level. This prunes duplicate branches at each depth level.</p>
<h3>Diagram 3 β N-Queens Pruning</h3>
<p><strong>N-Queens: Constraint-Based Pruning</strong><br>N=4: place 4 queens on a 4x4 board, no two attacking each other. Place one queen per row. For each row, try all columns.</p>
<pre class="trace-output">
Row 0: try col 0, 1, 2, 3.
Row 0, col 0:
Q . . .
Row 1, try cols:
col 0 (same col, PRUNE), col 1 (diagonal, PRUNE), col 2 (safe), col 3 (diagonal, PRUNE).
Row 1, col 2:
Q . . .
. . Q .
Row 2, try cols:
all attacked by Q at (0,0) or (1,2) -> all PRUNED. Backtrack to row 1.
Try col 3:
Q . . .
. . . Q
Row 2, col 1:
Q . . .
. . . Q
. Q . .
Row 3: cols 0 (col prune), 1 (col prune), 2 (diag prune), 3 (col prune).
All pruned. Backtrack to row 0, try col 1. ...
</pre>
<p><strong>Solutions found: 2 (for N=4).</strong></p>
<pre class="trace-output">
. Q . . . . Q .
. . . Q Q . . .
Q . . . . . . Q
. . Q . . Q . .
</pre>
<p><strong>Pruning criteria:</strong> same column, same diagonal (r1-c1 == r2-c2), or same anti-diagonal (r1+c1 == r2+c2).</p>
<h3>Diagram 4 β Combination Sum</h3>
<p><strong>Combination Sum: Pruning on Remaining Target</strong><br><code>candidates = [2, 3, 6, 7]</code>, <code>target = 7</code><br>Find all combinations that sum to target (reuse allowed).</p>
<pre class="trace-output">
start=0 (index), path=[], remaining=7
|
+-- pick 2, remaining=5
| +-- pick 2, remaining=3
| | +-- pick 2, remaining=1
| | | +-- pick 2, remaining=-1 PRUNE (negative)
| | | +-- pick 3, remaining=-2 PRUNE
| | +-- pick 3, remaining=0 SOLUTION: [2,2,3]
| | +-- pick 6, remaining=-3 PRUNE
| +-- pick 3, remaining=2
| | +-- pick 3, remaining=-1 PRUNE
| | (no more valid picks)
| +-- pick 6, remaining=-1 PRUNE
+-- pick 3, remaining=4
| +-- pick 3, remaining=1
| | +-- pick 3, remaining=-2 PRUNE
| +-- pick 6, remaining=-2 PRUNE
+-- pick 6, remaining=1
| +-- pick 6, remaining=-5 PRUNE
+-- pick 7, remaining=0 SOLUTION: [7]
</pre>
<p>Solutions: <code>[2,2,3]</code> and <code>[7]</code>.</p>
</section>
<!-- SECTION 3 -->
<section id="section-3" class="chapter-section">
<h2> 3 β Real-World Use Cases</h2>
<div class="table-responsive">
<table class="insight-table">
<thead>
<tr>
<th>Problem</th>
<th>Backtracking Application</th>
<th>Industry System</th>
</tr>
</thead>
<tbody>
<tr>
<td>Puzzle solving</td>
<td>Sudoku, crosswords, constraint satisfaction</td>
<td>Game engines, puzzle generators</td>
</tr>
<tr>
<td>Circuit layout</td>
<td>VLSI routing β place wires avoiding conflicts</td>
<td>EDA (Electronic Design Automation) tools</td>
</tr>
<tr>
<td>Regex matching</td>
<td>NFA simulation backtracks on failed matches</td>
<td>grep, database query engines, parsers</td>
</tr>
<tr>
<td>Natural language parsing</td>
<td>Earley/CYK parser explores grammar rules</td>
<td>NLP compilers, syntax highlighters</td>
</tr>
<tr>
<td>Test case generation</td>
<td>Enumerate all input combinations for coverage</td>
<td>Automated software testing frameworks</td>
</tr>
<tr>
<td>Scheduling</td>
<td>Assign tasks to slots satisfying constraints</td>
<td>University timetabling, exam scheduling</td>
</tr>
<tr>
<td>Cryptography</td>
<td>Key space enumeration for brute-force attacks</td>
<td>Security penetration testing tools</td>
</tr>
<tr>
<td>Combinatorial optimisation</td>
<td>TSP branch-and-bound with backtracking pruning</td>
<td>Logistics, route planning, supply chain</td>
</tr>
<tr>
<td>AI game playing</td>
<td>Minimax with alpha-beta pruning</td>
<td>Chess engines, Go AI (pre-neural era)</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- SECTION 4 -->
<section id="section-4" class="chapter-section">
<h2> 4 β Core Concepts & Algorithms</h2>
<h3>4.1 β Subsets (Power Set)</h3>
<p>Generate all 2^n subsets of nums. The start index prevents permutations of the same subset.</p>
<div class="ch-code-wrap">
```cpp
// LeetCode 78 β Subsets β O(2^n * n) Time, O(n) Space
class Solution {
void bt(vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& res) {
res.push_back(path); // every node is a valid subset
for (int i = start; i < (int)nums.size(); i++) {
path.push_back(nums[i]); // CHOOSE
bt(nums, i+1, path, res); // EXPLORE (i+1: no reuse)
path.pop_back(); // UN-CHOOSE
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
bt(nums, 0, path, res);
return res;
}
};
// Subsets II β with duplicates (LC 90)
// Sort first, then skip nums[i] == nums[i-1] at the same recursion level.
void btII(vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& res) {
res.push_back(path);
for (int i = start; i < (int)nums.size(); i++) {
if (i > start && nums[i] == nums[i-1]) continue; // skip duplicate
path.push_back(nums[i]);
btII(nums, i+1, path, res);
path.pop_back();
}
}
4.2 β Permutations
Order matters. We use a used[] array to track selections and loop from 0 every time.
bt(nums, used, path, res); // EXPLORE
path.pop_back(); // UN-CHOOSE
used[i] = false;
}
}
public:
vector<vector
// Permutations II β with duplicates (LC 47)
// Sort first. Skip nums[i]==nums[i-1] when nums[i-1] is NOT used.
// (This means we only take the first copy of a duplicate at each level.)
void btII(vector
</div>
<h3>4.3 β Combinations</h3>
<div class="ch-code-wrap">
```cpp
// LeetCode 77 β Combinations: choose k numbers from [1..n] β O(C(n,k) * k)
class Solution {
void bt(int n, int k, int start, vector<int>& path, vector<vector<int>>& res) {
if ((int)path.size() == k) {
res.push_back(path);
return;
}
// Pruning: need k-path.size() more elements, at most n-i+1 remain
for (int i = start; i <= n - (k - (int)path.size()) + 1; i++) {
path.push_back(i); // CHOOSE
bt(n, k, i+1, path, res); // EXPLORE
path.pop_back(); // UN-CHOOSE
}
}
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> res;
vector<int> path;
bt(n, k, 1, path, res);
return res;
}
};
4.4 β Combination Sum (Reuse Allowed)
for (int i = start; i < (int)cands.size(); i++) {
if (cands[i] > remain) break; // sorted: all further are larger, PRUNE
path.push_back(cands[i]);
bt(cands, i, remain - cands[i], path, res); // i (not i+1): reuse ok
path.pop_back();
}
}
public:
vector<vector
</div>
<h3>4.5 β N-Queens</h3>
<div class="ch-code-wrap">
```cpp
// LeetCode 51 β N-Queens β O(N!) Time, O(N) Space
// Place N queens on NΓN board so none attack each other.
class Solution {
vector<vector<string>> res;
vector<bool> col, diag1, diag2; // col, '/' diagonal, '\' diagonal
void bt(int row, int n, vector<string>& board) {
if (row == n) {
res.push_back(board);
return;
}
for (int c = 0; c < n; c++) {
// Prune: column or either diagonal is occupied
if (col[c] || diag1[row-c+n-1] || diag2[row+c]) continue;
board[row][c] = 'Q'; // CHOOSE
col[c] = diag1[row-c+n-1] = diag2[row+c] = true;
bt(row+1, n, board); // EXPLORE
board[row][c] = '.'; // UN-CHOOSE
col[c] = diag1[row-c+n-1] = diag2[row+c] = false;
}
}
public:
vector<vector<string>> solveNQueens(int n) {
col.assign(n,false);
diag1.assign(2*n-1,false); // '/' diagonals: indexed by row-col+n-1
diag2.assign(2*n-1,false); // '\' diagonals: indexed by row+col
vector<string> board(n, string(n,'.'));
bt(0, n, board);
return res;
}
};
4.6 β Word Search on Grid
char tmp = g[r][c];
g[r][c] = '#'; // CHOOSE: mark visited
bool found = bt(g,w,idx+1,r+1,c) || bt(g,w,idx+1,r-1,c) ||
bt(g,w,idx+1,r,c+1) || bt(g,w,idx+1,r,c-1);
g[r][c] = tmp; // UN-CHOOSE: restore cell
return found;
}
public:
bool exist(vector<vector
</div>
</section>
<!-- SECTION 5 -->
<section id="section-5" class="chapter-section">
<h2> 5 β Pattern Recognition Guide</h2>
<div class="table-responsive">
<table class="insight-table">
<thead>
<tr>
<th>Problem Type</th>
<th>Template Variation</th>
<th>Key Decisions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Subsets (no duplicates)</td>
<td>Collect at every node; loop from start</td>
<td>start index prevents reuse / duplicates</td>
</tr>
<tr>
<td>Subsets (with duplicates)</td>
<td>Sort + skip nums[i]==nums[i-1] at same level</td>
<td><code>i > start</code> guards same-level skip</td>
</tr>
<tr>
<td>Permutations (no dupes)</td>
<td>used[] array; loop from 0 every time</td>
<td>used[] prevents reusing same element</td>
</tr>
<tr>
<td>Permutations (with dupes)</td>
<td>Sort + skip when prev duplicate unused</td>
<td><code>!used[i-1]</code> ensures canonical ordering</td>
</tr>
<tr>
<td>Combinations (k of n)</td>
<td>Collect when path.size()==k</td>
<td>Upper bound prune: <code>i <= n-(k-path.size())+1</code></td>
</tr>
<tr>
<td>Combination sum (reuse)</td>
<td>Pass i (not i+1) to allow reuse</td>
<td>Sort + break when candidate > remain</td>
</tr>
<tr>
<td>Combination sum II</td>
<td>Pass i+1; skip duplicates at same level</td>
<td><code>i > start && nums[i]==nums[i-1]</code></td>
</tr>
<tr>
<td>N-Queens / Sudoku</td>
<td>Boolean arrays for constraints</td>
<td>col[], diag1[], diag2[] for O(1) check</td>
</tr>
<tr>
<td>Word search / grid</td>
<td>Mark cell visited; restore on backtrack</td>
<td><code>g[r][c]='#'</code> then restore to tmp</td>
</tr>
<tr>
<td>Palindrome partitioning</td>
<td>Collect when index == s.size()</td>
<td>isPalindrome check before recursing</td>
</tr>
<tr>
<td>Generate parentheses</td>
<td>Track open and close counts</td>
<td>open < n to add <code>'('</code>; close < open to add <code>')'</code></td>
</tr>
</tbody>
</table>
</div>
<div class="insight-box">
<h4>Backtracking Complexity Formula</h4>
<ul>
<li><strong>For subsets:</strong> O(2^n * n) β 2^n subsets, each copied in O(n).</li>
<li><strong>For permutations:</strong> O(n! * n) β n! permutations, each copied in O(n).</li>
<li><strong>For combinations C(n,k):</strong> O(C(n,k) * k) β C(n,k) results, each copied in O(k).</li>
<li><strong>For N-Queens:</strong> O(N!) with pruning significantly reducing the constant factor.</li>
<li><strong>For word search:</strong> O(M*N * 4^L) β M*N starting points, 4^L paths of length L.</li>
</ul>
<p>Backtracking is exponential by nature. Pruning reduces the constant but not the exponent. If a problem has overlapping subproblems AND only needs the count or optimal value (not all solutions), DP is almost always faster.</p>
</div>
<div class="insight-box warning">
<h4>Duplicate Handling Cheat Sheet</h4>
<ul>
<li><strong>SUBSETS</strong> with duplicates: sort nums. In the loop, skip if <code>i > start && nums[i] == nums[i-1]</code>.</li>
<li><strong>PERMUTATIONS</strong> with duplicates: sort nums. Skip if <code>i > 0 && nums[i] == nums[i-1] && !used[i-1]</code>.</li>
</ul>
<p><strong>Why different?</strong> <br>
Subsets: 'start' is the left boundary of the current level. <br>
Permutations: level always starts at 0, so check <code>!used[i-1]</code> to detect same-level duplicate. <br><br>
Golden rule: sort the input first, then skip consecutive duplicates AT THE SAME RECURSION LEVEL.</p>
</div>
</section>
<!-- SECTION 6 -->
<section id="section-6" class="chapter-section">
<h2> 6 β Complete C++ Implementations</h2>
<h3>6.1 β Generate Parentheses</h3>
<div class="ch-code-wrap">
```cpp
// LeetCode 22 β Generate Parentheses β O(4^n / sqrt(n)) Catalan number
class Solution {
void bt(int n, int open, int close, string& curr, vector<string>& res) {
if ((int)curr.size() == 2*n) { res.push_back(curr); return; }
if (open < n) { // can add '('
curr.push_back('(');
bt(n, open+1, close, curr, res);
curr.pop_back();
}
if (close < open) { // can add ')' only if open > close
curr.push_back(')');
bt(n, open, close+1, curr, res);
curr.pop_back();
}
}
public:
vector<string> generateParenthesis(int n) {
vector<string> res; string curr;
bt(n, 0, 0, curr, res);
return res;
}
};
6.2 β Palindrome Partitioning
for (int end = start; end < (int)s.size(); end++) {
if (!isPalin(s, start, end)) continue; // prune non-palindromes
path.push_back(s.substr(start, end-start+1));
bt(s, end+1, path, res);
path.pop_back();
}
}
public:
vector<vector
</div>
<h3>6.3 β Sudoku Solver</h3>
<div class="ch-code-wrap">
```cpp
// LeetCode 37 β Sudoku Solver β O(9^M) Time M=empty cells, O(M) Space
class Solution {
bool isValid(vector<vector<char>>& b, int r, int c, char d) {
for (int i=0;i<9;i++) {
if (b[r][i]==d || b[i][c]==d) return false;
if (b[3*(r/3)+i/3][3*(c/3)+i%3]==d) return false;
}
return true;
}
bool bt(vector<vector<char>>& b) {
for (int r=0;r<9;r++) {
for (int c=0;c<9;c++) {
if (b[r][c] != '.') continue;
for (char d='1'; d<='9'; d++) {
if (!isValid(b,r,c,d)) continue;
b[r][c] = d;
if (bt(b)) return true;
b[r][c] = '.';
}
return false; // no digit worked: backtrack
}
}
return true; // all cells filled
}
public:
void solveSudoku(vector<vector<char>>& board) {
bt(board);
}
};
7 β Complexity Reference
| Algorithm | Time (without pruning) | Space |
|---|---|---|
| Subsets (no duplicates) | O(2^n * n) | O(n) |
| Subsets II (duplicates) | O(2^n * n) pruning reduces const | O(n) |
| Permutations (no duplicates) | O(n! * n) | O(n) |
| Permutations II (duplicates) | O(n! * n) pruning reduces const | O(n) |
| Combinations C(n,k) | O(C(n,k) * k) | O(k) |
| Combination Sum (reuse) | O(N^(T/M)) T=target, M=min cand | O(T/M) |
| N-Queens | O(N!) heavily pruned in practice | O(N) |
| Sudoku Solver | O(9^M) M = empty cells | O(M) |
| Word Search | O(M*N * 4^L) L = word length | O(L) |
| Generate Parentheses | O(4^n / sqrt(n)) Catalan number | O(n) |
Backtracking is always exponential in the worst case β this is unavoidable for NP problems. The stated complexity is without pruning. With good pruning, practical performance can be orders of magnitude better. Space is O(depth of recursion tree) = O(n) for most problems β only the current path is stored on the stack.
8 β Solved Problem 1
Combination Sum
MediumGiven an array of distinct integers candidates and a target integer target, return all unique combinations of candidates where the chosen numbers sum to target. The same number may be chosen from candidates an unlimited number of times.
Observations
Since elements can be reused, this is not a standard subset problem. We pass the same index i (not i+1) to allow reuse.
- Key insight 1: Sort candidates first. If the current candidate exceeds the remaining target, all further candidates (which are larger) also exceed it β break early.
- Key insight 2: Use a start index to avoid generating duplicates like
[2,3]and[3,2]. By only considering candidates atindex >= start, we ensure combinations are in non-decreasing order.
Complexities
- Time: O(N^(T/M)) branching T/M deep where T=target, M=min element.
- Space: O(T/M) maximum recursion stack depth.
Dry Run (candidates = [2,3,6,7], target = 7)
Call start remain path Action
bt(0,7) 0 7 [] try 2,3,6,7
bt(0,5) 0 5 [2] try 2,3,6,7
bt(0,3) 0 3 [2,2] try 2,3,6,7
bt(0,1) 0 1 [2,2,2] try 2(>1 no), 3(break)
bt(1,1) 1 1 [2,2,3] 3>1, break. backtrack back to [2,2]
1 3 [2,2] pick 3: remain=0
remain==0 β 0 [2,2,3] SOLUTION! add to res
Final result: [[2,2,3], [7]]
9 β Solved Problem 2
N-Queens
HardPlace n queens on an n x n chessboard so that no two queens attack each other (no shared row, column, or diagonal). Return all distinct solutions as board configurations.
Observations
- Key insight 1: Place exactly one queen per row. This reduces the problem to choosing one column per row.
- Key insight 2: Three O(1) lookup arrays suffice for constraint checking:
col[],diag1[](indexed by row-col+n-1 for the '/' diagonal),diag2[](indexed by row+col for the '\' diagonal). - Pruning is critical: Without it, complexity is n^n. With column+diagonal pruning, the search space shrinks to approximtely n!. For n=8: 8^8 = 16M vs 8! = 40K.
Complexities
- Time: O(N!) β search space pruned heavily.
- Space: O(n) for the recursion stack and O(1) arrays.
10 β Common Mistakes & Edge Cases
10.1 β Structural Mistakes
- Forgetting the un-choose (backtrack) step. Without undoing the choice, the path accumulates garbage from previous branches.
- Passing
pathby value instead of by reference. This copies the path at every node β O(n) per call β making the algorithm significantly slower. Always pass by reference and usepush_back/pop_back. - For subsets, collecting results only at the leaf. Every node is a valid subset β collect at the beginning of every call.
- For combination sum with reuse, passing i+1 instead of i. This prevents reusing the same element and misses valid combinations.
10.2 β Duplicate Handling Mistakes
- Subsets/Combo sum: Skipping duplicates using
i > 0instead ofi > start. This skips valid paths where a duplicate appears deeper in the tree, not just at the same level. - Permutations: Skipping when
!used[i-1]without sorting first. The deduplication logic only works if identical elements are adjacent. - Confusing the skip conditions: Subsets use
i > start. Permutations usei > 0 && nums[i]==nums[i-1] && !used[i-1].
10.3 β Edge Cases
- Empty input: subsets of
[]=[[]](one empty subset). Always initialise result with empty and handle gracefully. - Single element: subsets of
[1]=[[], [1]]. Permutations of[1]=[[1]]. - Target = 0 for combination sum: the only solution is
[](empty combination).
11 β Common Interview Questions
| Problem | Key Implementation Detail |
|---|---|
| Subsets (LC 78) | Collect at every node, loop from start |
| Subsets II (LC 90) | Sort + skip i > start && nums[i]==nums[i-1] |
| Permutations (LC 46) | used[] array, loop 0 to N every time |
| Permutations II (LC 47) | Sort + skip when !used[i-1] |
| Combination Sum (LC 39) | Reuse allowed, sort + break prune, pass i |
| Combination Sum II (LC 40) | No reuse, pass i+1, skip duplicates i > start |
| N-Queens (LC 51) | col[], diag1[], diag2[] O(1) checks |
| Word Search (LC 79) | Mark visited with '#', restore on backtrack |
| Palindrome Partitioning (LC 131) | Check palindrome before recursing to prune |
Chapter 9 β Key Takeaways
- Backtracking =
choose+explore+un-choose. The un-choose step is non-negotiable. - Pruning is the difference between TLE and AC. Always prune invalid branches before recursing.
- Subsets: collect at EVERY node. Use start index.
- Permutations: collect at leaves only. Use
used[]array. Loop from 0. - Combination sum with reuse: pass
i(noti+1) to allow picking same element again. - If problem only needs COUNT or OPTIMAL VALUE and subproblems overlap, prefer DP over backtracking.