Choose → Explore → Unchoose. Build solutions incrementally. At dead end? Undo and try another path.
At each level, we decide: include this number or skip it. Every path from root to leaf = one subset.
[][]path.append(choice)backtrack(path, next_choices)path.pop() — this is what makes it backtracking!results.append(path[:]) — NOT results.append(path). List is a reference — if you append path itself, all results will be the same (empty) list at the end.| Problem | Key Difference | Complexity |
|---|---|---|
| Subsets of [1,2,3] | Use start index — only go forward | O(2ⁿ) |
| Permutations of [1,2,3] | Use used[] array — any unused element | O(n!) |
| Combinations (n choose k) | start + stop when path length = k | O(C(n,k)) |
start (not 0), we ensure [1,2] is generated but not [2,1]. Order doesn't matter for subsets.return immediately. Don't explore children that are guaranteed to fail. This can dramatically reduce the search space.def backtrack(path, start): if violates_constraint(path): return # prune! don't explore further if is_complete(path): results.append(path[:]) return ...
# Subsets — use start index def subsets(nums): res, path = [], [] def bt(start): res.append(path[:]) # add at every node for i in range(start, len(nums)): path.append(nums[i]) # choose bt(i + 1) # explore path.pop() # unchoose bt(0); return res # Permutations — use used[] array def permute(nums): res, path = [], [] used = [False] * len(nums) def bt(): if len(path) == len(nums): res.append(path[:]); return for i in range(len(nums)): if not used[i]: used[i] = True; path.append(nums[i]) bt() used[i] = False; path.pop() bt(); return res