跪拜 Guibai
← All articles
Algorithms · JavaScript · Deep Learning

A Three-Slot Template Turns Every Backtracking Problem into Fill-in-the-Blanks

By 烬羽 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Interview loops on LeetCode 46 are common because candidates treat each backtracking variant as a new problem instead of recognizing a single parameterized skeleton. Memorizing three diffs on one template eliminates the rewrite-from-scratch cycle and directly answers the standard follow-up: "Now modify it for combinations."

Summary

Backtracking is often rewritten from scratch by intuition, leading to the same bugs: missing undo steps, reference-copy failures, and forgotten termination conditions. A universal template isolates the three decision points that differ across problem types — when to record a result, where to begin the loop, and what to skip. Permutations use a `used[]` array and always start from zero; combinations and subsets use a `start` parameter that only moves forward, preventing duplicate sets. Subsets differ from combinations solely by recording at every node rather than only at a fixed depth.

The template exposes why `result.push([...path])` is mandatory in JavaScript: pushing the `path` reference means every stored result mutates when `pop()` runs later. The same structure handles deduplication for repeated elements by sorting and checking adjacent values. Decision trees make the time complexities visible — O(n!) for permutations, O(2^n) for subsets, and O(C(n,k)) for combinations — each corresponding to the number of nodes traversed.

Takeaways
All backtracking problems share one skeleton: termination check, loop over choices, make a choice, recurse, undo the choice.
Permutations require a `used[]` boolean array and always loop from index 0; combinations and subsets use a `start` parameter and only loop forward.
Subsets record a result on every recursive entry; combinations record only when the path reaches length k.
`result.push([...path])` is mandatory in JavaScript because `path` is a reference that later `pop()` calls will mutate.
Duplicate elements in the input are handled by sorting first, then skipping when `nums[i] === nums[i-1]` and `i > start`.
Time complexity maps directly to decision-tree node counts: O(n!) for permutations, O(2^n) for subsets, O(C(n,k)) for combinations.
Conclusions

The template reveals that the core difference between problem types is just the iteration domain — full set versus suffix — which is a simpler mental model than memorizing separate algorithms.

JavaScript's reference semantics turn a missing spread operator into a silent data-corruption bug that produces empty arrays without throwing an error, making it a disproportionately common failure in coding interviews.

Teaching backtracking through decision-tree diagrams makes the time complexity derivable by inspection: count the nodes, and you have the bound.

Concepts & terms
Backtracking
A depth-first search pattern that explores a decision tree by making a choice, recursing, and then undoing that choice to try the next branch. It is recursion plus a symmetrical undo step.
Decision tree in backtracking
A conceptual tree where each node represents a partial solution (the current path) and each edge represents adding a candidate. Leaves are complete solutions; backtracking traverses every node depth-first.
Pruning
Skipping branches that cannot lead to valid solutions — for example, skipping already-used numbers in permutations or skipping duplicate values in sorted input.
Reference vs. value copy in JavaScript
`result.push(path)` stores a reference to the mutable `path` array, so later `pop()` calls corrupt every stored result. `[...path]` creates a shallow copy that is immune to subsequent mutations.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗