Explore all possibilities. Choose β Explore β Unchoose.
Backtracking is controlled recursion. Instead of blindly generating everything, we build solutions one decision at a time, and the moment a partial solution can never lead to a valid answer, we abandon it (backtrack) and try the next option.
Why Backtracking?
Brute force generates ALL possible combinations, then filters valid ones.
Backtracking prunes invalid branches EARLY β explores only whatβs necessary.
Think of it as DFS on an implicit decision tree where bad branches get cut.
2οΈβ£ When to Think Backtracking π
βοΈ Strong Signals
βGenerate all subsets / combinations / permutationsβ
βFind all valid configurationsβ (N-Queens, Sudoku)
βAll ways to partition / splitβ
βGenerate all valid parenthesesβ
βWord search in a gridβ
Output is a list of lists
βοΈ Constraint Check
Input size is small (n β€ 20 for exponential, n β€ 10 for factorial)
The problem asks for exhaustive enumeration, not just count or optimal
There are constraints that let you prune (not just raw brute force)
Backtracking vs DP
Backtracking: βGive me ALL valid answersβ β enumerate
DP: βGive me the COUNT or OPTIMAL answerβ β optimize
Sometimes backtracking + memoization = DP (top-down).
But pure backtracking problems rarely need memo since test sizes are small.
3οΈβ£ Two Ways to Think About Backtracking
There are two equivalent mental models. Pick the one that clicks for you.
At each index, ask: βShould I take this element or not take it?β
This creates a binary tree β every node has exactly 2 children.
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TAKE / NOT-TAKE SKELETON β
β β
β At each index: β
β 1. TAKE β Include element, recurse β
β 2. NOT TAKE β Exclude element, recurse β
β β
β + BASE CASE β index == size β record answer β
β + PRUNING β Skip branches that can't work β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
At each level, loop through all remaining candidates and try each one.
Creates an n-ary tree β each node can have many children.
void backtrack(vector<int>& arr, int start, vector<int>& collect, vector<vector<int>>& result) { result.push_back(collect); // Collect at every node (for subsets) for (int i = start; i < arr.size(); i++) { collect.push_back(arr[i]); // 1οΈβ£ Choose backtrack(arr, i + 1, collect, result); // 2οΈβ£ Explore collect.pop_back(); // 3οΈβ£ Unchoose }}
When to use which?
Take/Not-Take is simpler to reason about β always 2 branches, base case at idx == n
For-Loop is more natural when you need to iterate over variable-length candidate lists (e.g., phone digits, variable choices per position)
Both produce the same results for subsets/combinations. Pick one and stick with it.
The Golden Rule of Backtracking
After the recursive call returns, the state MUST be exactly as it was before the call.
This is why we push_back then pop_back, swap then swap back, mark then unmark, pick then unpick
If you forget to unchoose, your entire recursion tree is corrupted.
4οΈβ£ Three Kinds of Recursion Questions
The same take/not-take tree can answer three different types of questions β the only thing that changes is the return type and base case.
Kind 1 β Print All (void)
Explore the entire tree. Collect every valid answer.
void solve(vector<int>& arr, int idx, vector<int>& ds, int sum, int k) { if (idx == arr.size()) { if (sum == k) result.push_back(ds); // β Found one β record it return; // Keep going, don't stop } ds.push_back(arr[idx]); // Take solve(arr, idx + 1, ds, sum + arr[idx], k); ds.pop_back(); // Undo solve(arr, idx + 1, ds, sum, k); // Not take}
Kind 2 β Print One (bool) β‘
Stop the moment you find the first valid answer. Huge time saving.
bool solve(vector<int>& arr, int idx, vector<int>& ds, int sum, int k) { if (idx == arr.size()) { if (sum == k) { print(ds); return true; // β Found β signal to stop } return false; // β Not valid β keep searching } ds.push_back(arr[idx]); if (solve(arr, idx + 1, ds, sum + arr[idx], k)) // Take return true; // π Answer found downstream β stop! ds.pop_back(); if (solve(arr, idx + 1, ds, sum, k)) // Not take return true; // π Answer found downstream β stop! return false; // Neither branch found anything}
The bool Pattern
if (recursive_call() == true) return true;
if (recursive_call() == true) return true;
return false;
As soon as any branch returns true, every parent immediately returns true β the recursion unwinds instantly without exploring the remaining tree.
Kind 3 β Count (int)
Donβt print anything. Just count how many valid subsequences exist.
int solve(vector<int>& arr, int idx, int sum, int k) { if (idx == arr.size()) { if (sum == k) return 1; // β This path is valid β count it return 0; // β Not valid β contributes 0 } int left = solve(arr, idx + 1, sum + arr[idx], k); // Take int right = solve(arr, idx + 1, sum, k); // Not take return left + right; // Total = sum of both branches}
The Count Pattern
return 1; β valid path
return 0; β invalid path
left + right; β parent adds counts from both children
This is the foundation of DP. Add memoization to this and you get top-down DP.
π Quick Reference
Question asksβ¦
Return Type
Base Case
How it stops
Print all answers
void
if (sum == k) β push to result
Explores entire tree
Print one answer
bool
return true / return false
Stops on first true
Count answers
int
return 1 / return 0
left + right at every node
π₯ Optimization (Positive Numbers Only)
If all elements are positive, you can prune early:
Once sum exceeds k, it can never decrease β no point exploring further.
5οΈβ£ The Three Problem Families
Almost every backtracking problem falls into one of three families.
Family
Take/Not-Take Style
Base Case
Order Matters?
Subsets
Take or skip each element
idx == n β collect
No
Combinations
Take or skip, but stop early at size k
size == k β collect
No
Permutations
Every unused element can go at each position
size == n β collect
Yes
Take / Not-Take Decision Tree (Subsets of [1, 2, 3])
idx=0
/ \
Take 1 Not take 1
/ \ / \
Take 2 Not take 2 Take 2 Not take 2
/ \ | | |
T3 NT3 T3 NT3 T3 NT3 T3 NT3
β β β β β β β β
{1,2,3}{1,2}{1,3}{1} {2,3}{2} {3} {}
β Collect at EVERY leaf (idx == n) β 2^n subsets
Key Insight
Subsets: Collect at every leaf β each path through the binary tree is one subset
Combinations: Same tree, but prune branches where size > k or remaining elements < needed
Permutations: Canβt use take/not-take directly β use for-loop + visited[] or swap instead, because order matters and every element must appear
β‘ Quick Complexity Guide
Here is the intuitive breakdown of backtracking time and space complexities.
1. Subsets (Power Set)
Time Complexity: O(2^N)
Why? There are N numbers and for each number, you make 2 decisions (either take or not take).
Example: for [1,2,3] you have 2 * 2 * 2 = 2^3 = 8 subsets. For N numbers, this generates 2^N subsets.
Space Complexity: O(N)
Max depth of the recursion tree is N (the number of elements).
2. Combinations (Choose R out of N)
Time Complexity: O(nCr)
Why? The total number of valid combination leaves is given by the combinations formula nCr (for example, choosing 2 elements out of N is nC2).
Space Complexity: O(R)
Recursion depth is capped at R since we stop as soon as we collect R elements.
3. Permutations (Arrangements)
Time Complexity: O(N * N!)
Why? For the 1st position you have N choices, for the 2nd you have N-1, then N-2, all the way down to 1 choice. This generates N! permutations.
We multiply by N because at each recursive step, we run a for-loop to iterate over all N candidates to see if they are visited.
Space Complexity: O(N)
Recursion stack goes up to depth N.
4. Grid Search / Maze (4-Directions DFS)
Time Complexity: O(4^L) or O(3^L)
Why? At any cell, you can step in 4 directions (Up, Down, Left, Right). For a path of length L, this creates 4^L potential paths. (If you cannot step back to where you just came from, it reduces to 3^L).
Space Complexity: O(L)
Recursion stack is equal to the length of the path L.
βοΈ Pruning β What Separates Backtracking From Brute Force
Brute force walks the entire decision tree. Backtracking cuts branches that provably canβt succeed. Every pruning trick in this note is one of three levers β learn to spot which one a problem hands you.
The pruning mindset
Before every recursive call, ask: βIs there any way this branch leads to a valid answer?β If provably not, return right now. The closer to the root you prune, the more of the tree you delete β a cut at depth 2 can erase millions of leaves at depth 20.
Given an array of distinct integers, return all possible subsets (the power set).
Intuition: At each element, we have 2 choices: take or not take. When weβve processed all elements (i == n), whatever is in collect is one valid subset.
// LC 78. Subsets (Take / Not-Take)class Solution {public: vector<vector<int>> ans; void solve(vector<int>& arr, int i, vector<int>& collect) { if (i == arr.size()) { ans.push_back(collect); return; } // β Take collect.push_back(arr[i]); solve(arr, i + 1, collect); collect.pop_back(); // β Not take solve(arr, i + 1, collect); } vector<vector<int>> subsets(vector<int>& nums) { vector<int> collect; solve(nums, 0, collect); return ans; }};// TC - O(n * 2^n) | SC - O(n) recursion depth
Good Follow Up:
Same as Subsets but input may contain duplicates. Result must not have duplicate subsets.
Intuition: Sort first. Then at each level, if we decide to Not Take the current element, we must skip all subsequent elements that are equal to it to avoid generating duplicate branches.
The Problem:
The soluation:
If we not take 2 then donβt take for future dublicate as well or it will give you same results which already came previously
// LC 90. Subsets II (Take / Not-Take)class Solution {public: vector<vector<int>> ans; void solve(vector<int> &nums, vector<int> &collect, int i) { if (i == nums.size()) { ans.push_back(collect); return; } // β Take collect.push_back(nums[i]); solve(nums, collect, i + 1); collect.pop_back(); // β οΈ Skip duplicates for the Not-Take branch while (i + 1 < nums.size() && nums[i] == nums[i + 1]) { i++; } // β Not take solve(nums, collect, i + 1); } vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<int> collect; sort(nums.begin(), nums.end()); // β οΈ MUST sort solve(nums, collect, 0); return ans; }};// TC - O(2^n) | SC - O(n)
7οΈβ£ Pattern 2 β Combinations
Same take/not-take structure as subsets, but we only collect results when collect.size() == k.
Return all permutations of an array of distinct integers.
Approach: Use unordered set for Visited Element or Mark the Original Array Element As visisted
// LC 46. Permutations (In-place INT_MAX visited approach)class Solution {public: vector<vector<int>> ans; void solve(vector<int>& nums, vector<int>& tem) { if (tem.size() == nums.size()) { // All elements placed ans.push_back(tem); return; } for (auto &i : nums) { // β loops through all elements if (i != INT_MAX) { // β INT_MAX means visited int store = i; tem.push_back(i); i = INT_MAX; // Mark as visited (Choose) solve(nums, tem); // Explore i = store; // Unmark (Unchoose) tem.pop_back(); } } } vector<vector<int>> permute(vector<int>& nums) { vector<int> tem; solve(nums, tem); return ans; }};// TC - O(n!) | SC - O(n)
Approach 2: Swap in place β the cleanest permutation code. Fix one position at a time by swapping each remaining element into it. No visited[], no extra array, no collect β nums itself is the answer when every position is fixed.
visited set / INT_MAX marking β most explicit, easy to read.
swap in place β least memory, but note it does not produce permutations in sorted order and breaks the simple duplicate-skip trick (a swap can bring an already-seen value back).
frequency map (below) β the natural choice the moment duplicates appear.
Input may have duplicates. Return only unique permutations.
Approach: Frequency Map
By building a frequency map of elements, we naturally avoid duplicates because we iterate over the unique keys of the map rather than indices of the array.
// LC 47. Permutations II (Frequency Map Approach)class Solution {public: vector<vector<int>> ans; void solve(unordered_map<int, int> &um, vector<int> &collect, int n) { if (collect.size() == n) { ans.push_back(collect); return; } for (auto &i : um) { int cnt = i.second; if (cnt != 0) { um[i.first]--; collect.push_back(i.first); solve(um, collect, n); // Explore collect.pop_back(); // Backtrack um[i.first]++; } } } vector<vector<int>> permuteUnique(vector<int>& nums) { unordered_map<int, int> um; vector<int> collect; int n = nums.size(); for (auto &i : nums) um[i]++; solve(um, collect, n); return ans; }};// TC - O(n!) | SC - O(n)
Alternative β sort + used[] (the editorial favorite)
Sort first, then at each level skip a value that equals its left neighbor when that neighbor isnβt currently used β meaning weβre at the same tree level and already tried it.
sort(nums.begin(), nums.end());// inside the for-loop over i:if (used[i]) continue; // already placed on this pathif (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue; // dup sibling β skip
The subtle part is !used[i-1]: if the previous equal element is used, the duplicates are on the same path (allowed); if itβs not used, theyβre sibling branches (a duplicate β skip). Same idea as the sort-and-skip trick in Subsets II, applied per level.
9οΈβ£ Pattern 4 β String Generation
These problems generate strings character-by-character, with constraints on what character can come next.
Generate all valid combinations of n pairs of parentheses.
Intuition: At each position, we have at most 2 choices:
Add ( if we havenβt used all n opening brackets
Add ) if closing count < opening count (ensures validity)
// LC 22. Generate Parenthesesvector<string> generateParenthesis(int n) { vector<string> result; backtrack(n, 0, 0, "", result); return result;}void backtrack(int n, int open, int close, string current, vector<string>& result) { if (current.size() == 2 * n) { // Base: placed all 2n characters result.push_back(current); return; } if (open < n) // Can still add ( backtrack(n, open + 1, close, current + "(", result); if (close < open) // Can add ) only if < open backtrack(n, open, close + 1, current + ")", result);}// TC - O(4^n / βn) β nth Catalan number | SC - O(n)
Why No Explicit Unchoose?
Weβre passing current + "(" as a new string (by value), not modifying current.
So the original current is unchanged when we return β the unchoose happens automatically.
This is a common shortcut with strings. Trade-off: more memory, but cleaner code.
Letter Combinations of a Phone Number β LC 17π₯
Map each digit to its letters. Generate all possible letter combinations.
// LC 17. Letter Combinations of a Phone Numbervector<string> letterCombinations(string digits) { if (digits.empty()) return {}; vector<string> phone = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; vector<string> result; backtrack(digits, phone, 0, "", result); return result;}void backtrack(string& digits, vector<string>& phone, int index, string current, vector<string>& result) { if (index == digits.size()) { // Processed all digits result.push_back(current); return; } string& letters = phone[digits[index] - '0']; for (char c : letters) { backtrack(digits, phone, index + 1, current + c, result); // No unchoose needed β current + c creates a new string }}// TC - O(4^n) where n = digits.length | SC - O(n)
Insert 3 dots into a digit string so it forms 4 valid octets (0-255, no leading zeros). Return all valid IPs.
Intuition: same βtry every splitβ idea as Palindrome Partitioning, but with a fixed number of parts (4) and a validity rule per segment. Each cut takes 1-3 digits; prune the instant a segment is invalid.
// LC 93. Restore IP Addressesclass Solution {public: vector<string> ans; // start = position in s, parts = octets placed, cur = IP built so far void solve(string& s, int start, int parts, string cur) { if (parts == 4) { if (start == s.size()) ans.push_back(cur.substr(0, cur.size() - 1)); // drop trailing '.' return; // used 4 parts, stop either way } for (int len = 1; len <= 3 && start + len <= s.size(); len++) { string seg = s.substr(start, len); if (len > 1 && seg[0] == '0') break; // β leading zero β this and longer are invalid if (stoi(seg) > 255) break; // β octet out of range solve(s, start + len, parts + 1, cur + seg + "."); } } vector<string> restoreIpAddresses(string s) { if (s.size() < 4 || s.size() > 12) return {}; // 4..12 digits or impossible solve(s, 0, 0, ""); return ans; }};// TC - O(3^4) β at most 3 choices Γ 4 parts | SC - O(1) depth
Fixed-count partitioning
Palindrome Partitioning splits into an unknown number of parts; Restore IP splits into exactly 4. Carry a parts counter and make the base case check both βused all partsβ and βconsumed the whole string.β The break (not continue) on an invalid segment is a real prune β a longer segment can only be worse.
π Pattern 5 β Grid / Board Search
These use backtracking on a 2D grid with in-place marking for visited cells.
Given a 2D board and a word, check if the word exists by adjacent cells (no reuse).
Backtracking pattern on grids:
Choose: mark cell as visited (e.g., board[r][c] = '#')
Explore: try all 4 directions
Unchoose: restore the original character
// LC 79. Word Searchbool exist(vector<vector<char>>& board, string word) { int rows = board.size(), cols = board[0].size(); for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { if (backtrack(board, word, r, c, 0)) return true; } } return false;}bool backtrack(vector<vector<char>>& board, string& word, int r, int c, int idx) { if (idx == word.size()) return true; // Found all characters // Boundary check + character match if (r < 0 || r >= board.size() || c < 0 || c >= board[0].size() || board[r][c] != word[idx]) return false; char saved = board[r][c]; board[r][c] = '#'; // Choose: mark visited // Explore: 4 directions bool found = backtrack(board, word, r + 1, c, idx + 1) || backtrack(board, word, r - 1, c, idx + 1) || backtrack(board, word, r, c + 1, idx + 1) || backtrack(board, word, r, c - 1, idx + 1); board[r][c] = saved; // Unchoose: restore return found;}// TC - O(m * n * 4^L) where L = word length | SC - O(L) recursion
In-Place Marking
We modify board[r][c] = '#' to mark it visited, then restore it.
This avoids needing a separate visited[][] matrix.
Some interviewers prefer the explicit visited matrix β both are valid.
Place n queens on an nΓn board so no two queens attack each other.
Approach: Place queens row by row. For each row, try every column. Before placing, check if the column, left diagonal, or right diagonal is already occupied.
// LC 51. N-Queensvector<vector<string>> solveNQueens(int n) { vector<vector<string>> result; vector<string> board(n, string(n, '.')); backtrack(board, 0, n, result); return result;}void backtrack(vector<string>& board, int row, int n, vector<vector<string>>& result) { if (row == n) { // All queens placed result.push_back(board); return; } for (int col = 0; col < n; col++) { if (isValid(board, row, col, n)) { board[row][col] = 'Q'; // Choose backtrack(board, row + 1, n, result); // Explore next row board[row][col] = '.'; // Unchoose } }}bool isValid(vector<string>& board, int row, int col, int n) { // Check column (rows above) for (int i = 0; i < row; i++) if (board[i][col] == 'Q') return false; // Check upper-left diagonal for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] == 'Q') return false; // Check upper-right diagonal for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] == 'Q') return false; return true;}// TC - O(n!) | SC - O(nΒ²)
O(1) Validity Check with HashSets
Instead of scanning the board each time, maintain three sets:
cols β columns that have a queen
diag1 β left diagonals (row - col is constant along a left diagonal)
diag2 β right diagonals (row + col is constant along a right diagonal)
Fill in empty cells so every row, column, and 3Γ3 box contains digits 1-9.
// LC 37. Sudoku Solvervoid solveSudoku(vector<vector<char>>& board) { backtrack(board);}bool backtrack(vector<vector<char>>& board) { for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) { if (board[r][c] != '.') continue; // Skip filled cells for (char num = '1'; num <= '9'; num++) { if (isValid(board, r, c, num)) { board[r][c] = num; // Choose if (backtrack(board)) // Explore β if solved, stop! return true; board[r][c] = '.'; // Unchoose } } return false; // No valid number β backtrack } } return true; // All cells filled}bool isValid(vector<vector<char>>& board, int row, int col, char num) { for (int i = 0; i < 9; i++) { if (board[row][i] == num) return false; // Row check if (board[i][col] == num) return false; // Column check // 3x3 box check int boxRow = 3 * (row / 3) + i / 3; int boxCol = 3 * (col / 3) + i % 3; if (board[boxRow][boxCol] == num) return false; } return true;}// TC - O(9^(empty cells)) worst case, heavily pruned | SC - O(81)
Key Difference from N-Queens
Sudoku needs exactly one solution β we return true as soon as one is found.
N-Queens collects all solutions β we donβt short-circuit.
This return true / return false pattern is common in βfind ONE valid configurationβ problems.
Can nums be split into k subsets with equal sums? This is the problem where pruning is the difference between passing and TLE.
Idea: each subset must sum to total / k. Fill one bucket at a time up to target; when a bucket is full, recurse to build the next one. used[] tracks consumed elements.
// LC 698. Partition to K Equal Sum Subsetsclass Solution {public: vector<bool> used; int target, n; bool solve(vector<int>& nums, int k, int start, int curr) { if (k == 1) return true; // last bucket auto-balances if (curr == target) // bucket full β start the next one return solve(nums, k - 1, 0, 0); for (int i = start; i < n; i++) { if (used[i] || curr + nums[i] > target) continue; // βοΈ feasibility if (i > start && nums[i] == nums[i-1] && !used[i-1]) continue; // βοΈ duplicate used[i] = true; if (solve(nums, k, i + 1, curr + nums[i])) return true; used[i] = false; if (curr == 0) break; // βοΈ this num can't even START an empty bucket β hopeless } return false; } bool canPartitionKSubsets(vector<int>& nums, int k) { int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % k) return false; // can't divide evenly target = sum / k; n = nums.size(); used.assign(n, false); sort(nums.rbegin(), nums.rend()); // big elements first β prune sooner if (nums[0] > target) return false; // one element already too big return solve(nums, k, 0, 0); }};// TC - O(k * 2^n) worst case, heavily pruned | SC - O(n)
All three pruning levers in one problem
This is the showcase for the pruning table above: feasibility (curr + nums[i] > target), duplicate (skip equal siblings), and a clever bound prune β if the current element canβt start an empty bucket (curr == 0), no arrangement ever will, so bail out entirely. Sorting descending makes all three fire earlier.
π§ Master Cheatsheet
The Decision Framework
What does the problem ask?
β
βββ "All subsets / subsequences"
β βββ SUBSETS β Take/Not-Take, collect at every leaf (idx == n)
β
βββ "All combinations of size k" or "that sum to target"
β βββ COMBINATIONS β Take/Not-Take, collect when size == k
β βββ Can reuse? β Take stays at idx, Not-Take moves to idx+1
β βββ No reuse? β Take moves to idx+1, Not-Take moves to idx+1
β
βββ "All permutations / arrangements"
β βββ PERMUTATIONS β For-loop from 0, use visited[] or swap
β
βββ "Generate valid strings" (parentheses, phone letters)
β βββ STRING GENERATION β constrained choices at each position
β
βββ "Find word in grid / explore all paths"
β βββ GRID BACKTRACKING β in-place marking, 4-directional DFS
β
βββ "Place queens / solve puzzle"
βββ CONSTRAINT SATISFACTION β heavy pruning, isValid check
Has duplicates?
βββ Subsets / Combinations β sort + while loop index skip: `while (i+1 < size && arr[i] == arr[i+1]) i++;`
βββ Permutations β Use Frequency Map (`unordered_map<int, int> um`) to naturally avoid duplicate paths
Pattern Recognition Table
Signal in problem
Pattern to use
βAll subsetsβ / βpower setβ
Take/Not-Take, collect at leaf
βAll combinations of k elementsβ
Take/Not-Take, stop at size k
βNumbers summing to targetβ
Take/Not-Take, take stays at idx
βAll permutationsβ / βall orderingsβ
For-loop from 0, visited[] or swap
βGenerate parenthesesβ / βvalid stringsβ
String generation β branching rules
βPartition into palindromesβ
Partitioning β try all splits
βSplit string into valid partsβ (IP addr)
Fixed-count partitioning + validity
βWord search in gridβ
Grid backtracking β 4-dir DFS
βN-Queensβ / βSudokuβ
Constraint satisfaction β prune hard
βSplit into k equal-sum groupsβ
Bucket fill + all 3 pruning levers
The Three Key Tweaks (Take / Not-Take Style)
1. WHAT DOES "TAKE" DO?
- No reuse: Take β idx + 1 (move forward)
- With reuse: Take β idx (stay β can take again)
2. WHAT DOES "NOT TAKE" DO?
- Always: Not-Take β idx + 1 (move to next element)
3. WHEN DOES TAKE/NOT-TAKE NOT WORK?
- Permutations β order matters, every element must appear
- Use For-Loop + visited[] or swap instead
Common Gotchas
1. Forgot to sort before deduplication β duplicates still appear
2. Did duplicate skipping on the Take branch instead of the Not-Take branch β skips valid branches
3. Forgot to check array boundaries in the duplicate skipping while loop (`i + 1 < size`) β out of bounds crash
4. Forgot to unchoose β entire tree is corrupted
5. Permutations: loop from start instead of 0 β missing permutations
6. Grid: forgot to restore cell after DFS β later paths wrongly blocked
7. Sudoku: forgot "return false" after trying all 9 digits β no backtracking
8. Passing vector by value when you mean by reference β unchoose not needed but O(n) copy per call. Know the trade-off.
9. Fixed-count partition (Restore IP): base case checks only "parts used" but forgets "whole string consumed" β accepts short IPs.
10. Swap-based permutations combined with the sort-and-skip dedup β swaps destroy the sorted order, so the dedup silently fails. Use a frequency map or `used[]` for duplicates.