Backtracking Master Notes.md
Backtracking Master Notes.md
Practical, interview-focused. Zero fluff theory, maximum application. Combines Hinglish quick-recall notes with detailed
English explanations.
1. First Principles
"All possible ways/combinations/permutations": Agar question bole return all valid combinations, subsets, ya paths.
Constraint Satisfaction: Ek specific arrangement dhoondni hai jo strict rules follow kare (Sudoku, N-Queens).
Exponential limits: Constraints dekho. Agar N ≤ 20, toh maximum chances hain ki O(2^N) ya O(N!) wali
backtracking lagegi.
Term Meaning
DFS Traversal style — explore one branch fully before another / ghusna till the end.
"Smart Brute Force" — go down a path; if it hits a dead end or invalid condition, undo and go back without
Backtracking
traversing the full path (Pruning).
In interview language: recursion is the tool, DFS is the traversal style, brute force is the search space, backtracking is the
optimized search strategy (brute force + early rejection + undoing state).
Exact Thinking Process (Before Coding)
1. What am I trying to build? — Output format kya hai? (2D vector, single string, answer list, one answer, count,
boolean, etc.)
2. What is the current state? — Current index, path, remaining target, visited, board position.
3. What choices do I have right now? — Element ko lu ya chhodu? Pick/skip/place/move/swap/partition. Kahan place
karu?
4. What is invalid? — Out of bounds, duplicate, sum too big, rule broken.
5. What is the base case / Goal? — Kahan rukna hai? When do I stop and store the answer?
6. What must be undone? — Path changes, visited marks, swaps, chosen elements.
2. State Identification
Examples: current index, current path/partial answer, remaining sum/target, visited array, board configuration, current
cell in grid, used-numbers mask.
current_sum / remaining_target
visited array/board
counts / mask
Grid dimensions
Constraint definitions
Primitive types that must differ per branch: Heavy/shared structures: vector<int>& nums , vector<int>&
index , current_sum , small counts/masks temp , vector<vector<int>>& ans , board, visited matrix
Use when state is small and an independent Use when the object is shared + modified + must be undone — avoids
copy is conceptually simpler (no undo needed) O(N) copy time/space per call
Practical rule: if the object is shared + modified + undone → pass by reference. If it's small + independent copy is fine
→ pass by value.
Globally-fixed values that don't need re-passing (but avoid true global variables in competitive programming
anyway — pass constants by reference instead)
1. Choices — Current index/state par main kya action le sakta hu? (Pick/Not Pick, 1 to 9 numbers, 4 directions,
place/move/swap/cut/add-operator)
2. Constraints — Kya ye choice allowed hai? (e.g. remaining sum < 0 → invalid; placement conflicts; out of grid;
substring not palindrome)
3. Goal / Base Case — Result kab store karna hai? (index == n, target == 0, full permutation built, board filled,
destination reached)
4. Pruning — Kab branch turant kill karni hai? (sorted array + current element already > remaining target → break;
duplicate branch already explored; sum too large; unsafe cell; row/col/diag conflict)
5. Undo Operation — Agar temp.push_back() kiya recursion se pehle, wapas aate time temp.pop_back() karna hi
padega. Similarly: visited[i][j] = false , re- swap() , restore board cell.
4. What is invalid?
8. Do I need pruning?
Recognize it when: every element gets exactly two decisions (include/exclude), order doesn't matter, each element
considered once.
Thinking: Har index wale element se pucho: "Tujhe temp mein aana hai ya nahi?"
// pick
path.push_back(nums[i]);
solve(i + 1, nums, path, ans);
path.pop_back();
// don't pick
solve(i + 1, nums, path, ans);
}
Common mistakes: forgetting base case, missing undo, using a loop unnecessarily.
Used for: Combination Sum, Permutations, placing items in K slots, subsets with controlled order.
Recognize it when: one recursion level chooses from many candidates using a start index; order among chosen
items shouldn't repeat.
Thinking: Ek fixed position ke liye N elements choices hain — loop i se N tak chalao.
temp.push_back(nums[i]); // DO
solve(i + 1, nums, temp, ans); // RECURSE (use i instead of i+1 if reuse allowed)
temp.pop_back(); // UNDO
}
}
Duplicates handling: if array sorted and nums[i] == nums[i-1] , continue to avoid duplicate subsets.
Common mistakes: using i+1 vs start+1 incorrectly, missing duplicate skip, confusing combination with
permutation logic.
Recognize it when: same set, different ordering; each element used exactly once per arrangement; needs visited[]
or swap technique.
Common mistakes: forgetting used[i] = 0 , generating duplicate permutations, confusing with combinations.
Recognize it when: order irrelevant, start index matters, k often given explicitly.
void solve(int start, int k, vector<int>& nums, vector<int>& path, vector<vector<int>>& ans) {
if ([Link]() == k) {
ans.push_back(path);
return;
}
for (int i = start; i < [Link](); i++) {
path.push_back(nums[i]);
solve(i + 1, k, nums, path, ans);
path.pop_back();
}
}
Common mistakes: using a permutation template, incorrect base case, forgetting the k stop condition.
Common mistakes: forgetting to include the empty subset, missing duplicate handling.
Pattern F — K Selections
Used for: picking exactly k elements — fixed-size groups (Combination Sum III, k-subsets).
Thinking: if [Link]() == k , store the answer; otherwise choose from remaining candidates.
Used when: input has repeated values and answers must stay unique (Subsets II, Combination Sum II, Permutations
II).
Skipping rule:
This prevents exploring the same value at the same decision level more than once — it kills ~50% of duplicate
branches before they even start (constraint propagation).
Common mistakes: skipping duplicates at the wrong level, not sorting, incorrect skip logic in permutations.
Used for: Combination Sum, Subset Sum, target count, exact-value selection.
Recognize it when: problem mentions "target", "sum", "remaining"; must hit exact value; overshoot can be pruned.
void solve(int start, int target, vector<int>& nums, vector<int>& path, vector<vector<int>>& ans)
{
if (target == 0) {
ans.push_back(path);
return;
}
if (target < 0) return;
for (int i = start; i < [Link](); i++) {
path.push_back(nums[i]);
solve(i, target - nums[i], nums, path, ans); // i → reuse allowed; i+1 → no reuse
path.pop_back();
}
}
Sorting + Bounding: sort array first — if current_sum + nums[i] > target , then break the loop instead of recursing
further (all subsequent elements will also overshoot).
Common mistakes: wrong start value for reuse vs non-reuse, forgetting target pruning, missing duplicate handling.
Pattern I — Partitioning
Used for: cutting a string/array into valid chunks (Palindrome Partitioning, Restore IP Addresses).
Recognize it when: "partition string", cut positions matter, each segment has a validity rule.
Thinking: current index is start ; loop end from start to string-end; if substring(start, end) is valid, add it to
the path and recurse from end + 1 .
Common mistakes: wrong substring indices, checking validity too late, missing base case.
LeetCode: 131. Palindrome Partitioning, 93. Restore IP Addresses, Word Break (partitioning mindset).
Used for: mazes, flood fill, path finding, Word Search, all routes in a matrix.
Recognize it when: 2D board, moves in 4 (or 8) directions, visited needed to avoid cycles.
Thinking: har cell se 4 directions (i+1,j) , (i-1,j) , (i,j+1) , (i,j-1) . Mark visited before going, un-mark on the
way back so other paths can reuse that cell.
void solve(int i, int j, vector<vector<int>>& grid, vector<vector<int>>& vis, string& path,
vector<string>& ans) {
if (i == [Link]() - 1 && j == grid[0].size() - 1) {
ans.push_back(path);
return;
}
vis[i][j] = 1;
for (auto [di, dj, ch] : moves) {
int ni = i + di, nj = j + dj;
if (isSafe(ni, nj, grid, vis)) {
path.push_back(ch);
solve(ni, nj, grid, vis, path, ans);
path.pop_back();
}
}
vis[i][j] = 0;
}
Common mistakes: forgetting to reset visited, wrong boundary check, infinite loops.
Used for: matching a word inside a board via adjacent moves, without reusing a cell.
State: board position, current word index, visited state (often done in-place by marking the board cell itself).
Common mistakes: forgetting to restore the board/visited cell, matching the wrong character index, not stopping once
the full word is found.
Used for: exploring all simple paths / all valid routes in a graph.
Recognize it when: graph input, "all paths", no revisiting nodes within the same path, path enumeration.
Thinking: node is the state; mark visited within the current path; recurse into neighbors; undo the visited mark on
return.
Used for: structures that must satisfy many simultaneous rules (Sudoku, N-Queens, M-Coloring).
Thinking: choose a cell → try a candidate → check row/col/diag/box (or adjacency) constraints via isValid() →
recurse → undo.
board[row][c] = 'Q';
col[c] = diag[row + c] = anti[row - c + n - 1] = 1;
board[row][c] = '.';
col[c] = diag[row + c] = anti[row - c + n - 1] = 0;
}
}
Common mistakes: wrong diagonal index formula, missing undo, placing more than one queen per row.
State: start index, path of substrings. Pruning: skip immediately if the substring isn't a palindrome.
Used for: inserting operators ( + - * ) between digits and evaluating on the fly (Expression Add Operators).
State: current index, current expression string, current running value, last operand (needed to correctly undo/handle
multiplication precedence).
Thinking: at each step, extend the current number or choose an operator, updating the running total; take care with *
since it needs the last operand to "undo" the previous addition.
Any other interview variant still reduces to the same base loop: choose from multiple candidates → obey constraints
→ recurse → undo → prune.
Pruning = cutting useless recursive branches early to avoid TLE (Time Limit Exceeded).
Core idea: if one branch is impossible, every child of that branch is also impossible — so pruning saves exponential
work.
Sorting + Bounding: Array ko pehle sort kar lo. E.g. target = 10, current sum = 8. If next element is 5 (8+5=13>10), all
subsequent elements (being ≥5 after sorting) will also overshoot — break the loop instead of recursing further.
Duplicate Skipping (Constraint Propagation): in a sorted array, if (i > start_index && nums[i] == nums[i-1])
continue; — kills ~50% of duplicate branch trees before they even start.
Early Return: if only a true/false answer is needed (e.g. "is Sudoku solvable"), return true immediately up the call
stack as soon as one valid answer is found — don't wait to collect into ans .
[ ] (index=0)
/ \
(Pick 1) / \ (Don't Pick 1)
[1] [ ] (index=1)
/ \ / \
(P 2) / (DP 2) (P 2) (DP 2)
[1,2] [1] [2] [ ] (index=2 -> Base Case Hit!)
start
├─ choose 1
│ ├─ choose 2
│ └─ choose 3
├─ choose 2
└─ choose 3
Permutation tree (shape): each level tries every still-unused element, branching into all remaining orderings before
returning back up.
1. temp.push_back(x) → DO
2. solve(...) → RECURSE
Scan this table whenever a new question appears — identify the pattern before writing code.
State
# Problem Name Pattern Choices Pruning / Base Case
(Params)
5 Combination Sum For-loop idx, target Pick same nums[i] target < 0 (Prune)
Combination Sum
7 For-loop k, target, num Pick 1 to 9 k == 0 check target
III
Letter
9 For-loop idx of digits Chars of digit[i] idx == [Link]()
Combinations
Place match in 1 of
19 Matchsticks to Sq Target Sum idx, sides[4] side > target / Sort desc
4
State
# Problem Name Pattern Choices Pruning / Base Case
(Params)
Substring to
21 Splitting a String Partitioning idx, prev_val num != prev_val - 1
number
Diff Ways to
24 Partitioning left, right Split at operators Return evaluated list
Compute
Optimal Account
25 Constraint idx Settle debt with i Skip if opposite signs
Bal
Factor
26 For-loop n, start_factor Divisors of n n % i != 0
Combinations
Obstacles, empty_cnt !=
27 Unique Paths III Grid DFS i, j, empty_cnt 4 directions
0
Missing intermediate
29 Android Unlock Grid DFS curr, len Next digit 1-9
numbers
Subset Sums
30 Pick/Not Pick idx, sum Add/Don't Add idx == N
(GFG)
idx, curr_sum,
33 Tug of War Subset/Diff Assign to Set 1/2 Maintain N/2 size limit
cnt
Remove Invalid
36 Graph/BFS+Backtrack str Remove ( or ) Valid parenthesis check
Par.
Target = 24 within
40 24 Game Simulation array size 4 Pick 2, apply ops
epsilon
41 Stickers to Spell For-loop target, freq Apply sticker chars Bitmask state pruning
Regular Exp
42 Pick/Not Pick i, j Match * or . String lengths
Match
Wildcard
43 Pick/Not Pick i, j Treat * as 0 or N String lengths
Matching
Confusing
45 For-loop curr_num Append 0,1,6,8,9 num > N
Number II
One-to-one mapping
46 Word Pattern II Partitioning idx_s, idx_p Map char to substr
breaks
Return excess
47 Distribute Coins Tree Backtrack node Base case: Null
coins
Missing UNDO ( pop_back() ): C++ arrays/vectors get modified in place. If not un-modified, the next branch
inherits the previous branch's leftover state.
Passing by Value in recursion: passing vector<int> temp (no & ) copies the vector at every recursion depth —
turns O(2^N) into O(N · 2^N) and risks MLE (Memory Limit Exceeded).
Wrong Base Case: forgetting return when the goal is met → infinite recursion → segmentation fault.
Global Variables Mess: e.g. a global visited matrix not cleared before the next test case.
String Pass by Value vs Reference: temp + char passed by value makes C++ create a new string each time
(auto-copies and auto-undoes). If string& temp is passed by reference instead, you must explicitly pop_back()
to undo.
Recognition flowchart:
4. Are there duplicate elements? → Sort the array → if (i > index && nums[i] == nums[i-1]) continue;
Dry-run trick: always take N = 3 as input and trace the first branch all the way to the base case, then step back up
one level and try the second path. Doing this on paper makes the code's structure completely clear before you type it.