Problem overview
The method permute receives an int[] called nums of distinct integers and returns every permutation: every way to put all the numbers in a row, each exactly once.
The order of the permutations is fixed. The positions are filled from first to last, always trying the numbers in their order in nums. Every permutation starting with nums[0] comes first, then those starting with nums[1], and so on, and the same rule applies to the second position inside each group. For [1, 2, 3]:
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
The numbers are tried in their order in nums, not in ascending order. For [3, 1, 2] the result is [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]], which starts with nums itself and ends with nums reversed. For [1] it is [[1]].
Approach: fill one position at a time
A permutation of n numbers has n positions. The first can hold any of the n numbers, the second any of the n - 1 that are left, and so on. That gives n! permutations, and any solution has to produce all of them.
Backtracking builds one permutation position by position. It keeps three things:
- a list
current, the permutation built so far; - a
boolean[]calledused, with one entry per number, saying whethernums[i]is already incurrent; - the
resultlist that collects the finished permutations.
To fill the next position, go through the numbers from index 0 and skip the used ones. For each free number, take it (mark it used, add it to current), fill the remaining positions with a recursive call, then give it back (remove it, unmark it) so the next number can take its place. When current holds all n numbers, save a copy of it. Because the loop always starts at 0 and goes up, the permutations come out in the order the statement describes.
The undo steps are the "back" in backtracking. The same current and used are shared by all the calls, so each call has to leave them as it found them.
Step by step
- Create
resultas aList<List<Integer>>holding a newArrayList<>(). - Create
usedwithnew boolean[nums.length]. A new boolean array starts with every elementfalse. - Start the search:
backtrack(nums, used, new ArrayList<>(), result). Nothing is placed yet and every number is free. - Return
result, which the helper has filled. - Write the helper.
backtrackreturnsvoidand takesnums,used,currentandresult. Each call places one more number and calls itself for the rest. - Stop when
current.size() == nums.length. The list holds every number once, so it is a whole permutation. - Save a copy,
new ArrayList<>(current), and return. The sharedcurrentwill change, so the result needs its own snapshot. - Loop with
ifrom 0 whilei < nums.length: the numbers that could fill the next position, always in the order ofnums. - Skip used numbers. If
used[i]istrue,continuegoes on to the nexti. - Mark it: set
used[i]totrue, so deeper calls won't take it. - Add it to the end of
current. - Recurse to fill the remaining positions. Nothing needs passing except the same four arguments.
- Remove it:
current.remove(current.size() - 1)drops the last number. - Unmark it: set
used[i]back tofalse, so later permutations can use that number in another position.
The solution in Java
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, used, new ArrayList<>(), result);
return result;
}
void backtrack(int[] nums, boolean[] used, List<Integer> current, List<List<Integer>> result) {
if (current.size() == nums.length) {
result.add(new ArrayList<>(current));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
used[i] = true;
current.add(nums[i]);
backtrack(nums, used, current, result);
current.remove(current.size() - 1);
used[i] = false;
}
}
}
Java details worth a closer look:
boolean[]and shared state. Arrays and lists are passed to methods by reference, so every call works on the sameusedandcurrent. That is why the undo lines matter.- Copy before saving.
result.add(current)without the copy stores the one shared list. After the search it is empty, so the call on[1, 2, 3]returns six empty lists:[[], [], [], [], [], []]. remove(int). With anintargument,List.removeremoves the element at that index.current.size() - 1is the index of the last element. For aList<Integer>and a value you'd writeremove(Integer.valueOf(x))instead.continue. It skips the rest of the loop body for thisionly. The loop itself carries on with the next index.
Complexity
Time: O(n * n!). There are n! permutations, and copying each takes n steps. The calls that build them are fewer than e * n! in all, and each loops over the n numbers, which stays within the same bound.
Space: O(n) besides the answer. The helper keeps current, used and one call per position, so everything is at most n long or deep.
Edge cases and common mistakes
- One number.
[1]gives[[1]]: one number can be placed only one way. - Two numbers.
[-10, 10]gives[[-10, 10], [10, -10]]. Negative values need no special care. - The size. With 8 numbers, the largest input allowed, the method returns
40320permutations. - Not unmarking. If
used[i] = falseis left out, numbers stay marked after their first use and the search stops finding most permutations. For[1, 2, 3]that version returns only[[1, 2, 3]]. - Not removing from
current. The list grows pastnums.length, so the size check no longer matches and results are wrong. - Not copying. The result holds the same list many times, as shown above.
- Starting the loop above 0. Numbers before the start index could never fill later positions, and some permutations would be missing.
Testing
nums = [1, 2, 3]gives[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]].nums = [3, 1, 2]gives[[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]].nums = [1]gives[[1]].
