A complete visual walkthrough of the backtracking approach — building a decision tree to generate every arrangement.
To generate all permutations, we need to place each element in each possible position. For n elements, the first position has n choices, the second has n-1, and so on.
We can't avoid generating all n! permutations — that's the output size. But we can generate them efficiently using a systematic exploration strategy.
Backtracking is the standard template for exhaustive enumeration problems. Build a candidate solution incrementally, and "undo" (backtrack) each choice before trying the next one. This avoids creating n! separate arrays upfront.
We maintain a path (current partial permutation) and a used boolean array to track which elements are already placed.
path to the result.nums[i] where used[i] == false: mark it used, add to path.backtrack() recursively to continue building the permutation.used[i] = false. Now try the next element.Why copy the path? We add new ArrayList<>(path) because path is mutated during backtracking. Without the copy, all entries in the result would reference the same (eventually empty) list.
Each level of the tree represents choosing which element to place at that position. Leaves are complete permutations.
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> result = new ArrayList<>(); backtrack(nums, new boolean[nums.length], new ArrayList<>(), result); return result; } private void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> result) { // Base case: all positions filled if (path.size() == nums.length) { result.add(new ArrayList<>(path)); // copy! return; } // Try each unused element for (int i = 0; i < nums.length; i++) { if (used[i]) continue; // skip used // Choose used[i] = true; path.add(nums[i]); // Explore backtrack(nums, used, path, result); // Unchoose (backtrack) path.remove(path.size() - 1); used[i] = false; } } }
Enter distinct integers and watch the backtracking algorithm build permutations step by step through the decision tree.
We generate all n! permutations, and copying each one into the result takes O(n). The recursion stack and used array use O(n) space (not counting the output).
This is optimal for generating permutations. Since there are n! permutations and we must output each one (of length n), no algorithm can do better than O(n! * n). The backtracking approach achieves this bound with minimal overhead.