Problem overview
The method Permute receives an int[] called nums of distinct numbers and returns every permutation: every way to put all the numbers in a row, each exactly once. The result is a List<List<int>>.
The order is fixed too. Fill the positions from first to last and always try the numbers in their order in nums. For {1, 2, 3} that gives [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]: two permutations start with 1, then two with 2, then two with 3. For {3, 1, 2} the numbers are tried as 3, then 1, then 2, not in ascending order, so the result is [[3,1,2],[3,2,1],[1,3,2],[1,2,3],[2,3,1],[2,1,3]].
Approach: fill one position at a time
With n numbers there are n choices for the first position, n - 1 for the second, and so on, which gives n! permutations. Backtracking builds them position by position.
The search keeps two things: a list current with the permutation built so far, and a bool[] called used where used[i] says whether nums[i] is already in current. To fill the next position, it goes through the numbers from index 0 and skips the used ones. For each free number it:
- takes it: marks it used and adds it to
current; - fills the remaining positions with a recursive call;
- gives it back: removes it from
currentand marks it unused, so the next number in the loop can try the same position.
When current holds n numbers, it is a whole permutation, and the search saves a copy. Because the loop always runs from index 0 in the order of nums, the permutations come out in exactly the order the lesson asks for.
Take {1, 2, 3}. The first position tries 1; the second position then skips the used 1 and tries 2, then the third position takes 3, which saves [1,2,3]. Giving back 3 and 2, the second position tries 3, and the third takes 2, which saves [1,3,2]. Then the first position gives back 1 and tries 2, and so on.
Step by step
- Create
result, an emptyList<List<int>>. - Create
usedasnew bool[nums.Length]. A newboolarray starts with every elementfalse: nothing is taken yet. - Start the search with
Backtrack(nums, used, new List<int>(), result): no position is filled and every number is free. - Return
result.Backtrackfilled this same list. - Write
Backtrackas apublic voidmethod takingnums,used,currentandresult. Each call fills one more position and calls itself for the rest. - Check for a full permutation:
current.Count == nums.Lengthmeans every number is in it exactly once. - Save a copy with
result.Add(new List<int>(current))andreturn. A snapshot is needed becausecurrentkeeps changing. - Loop over the candidates:
for (int i = 0; i < nums.Length; i++), always from 0, to get the required order. - Skip used numbers with
if (used[i]) { continue; }. A number already incurrentcan't appear twice. - Mark it:
used[i] = true, so the deeper calls won't take it. - Add it to the end of
currentwithAdd. - Recurse with the same four arguments. Nothing else needs passing, since
usedandcurrentalready say what is taken. - Give the number back:
current.RemoveAt(current.Count - 1). - Unmark it:
used[i] = false, so permutations built later can use it in another position.
The solution in C#
class Solution {
public List<List<int>> Permute(int[] nums) {
List<List<int>> result = new List<List<int>>();
bool[] used = new bool[nums.Length];
Backtrack(nums, used, new List<int>(), result);
return result;
}
public void Backtrack(int[] nums, bool[] used, List<int> current, List<List<int>> result) {
if (current.Count == nums.Length) {
result.Add(new List<int>(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.RemoveAt(current.Count - 1);
used[i] = false;
}
}
}
The C# details worth knowing:
- Arrays and lists are shared by reference.
used,currentandresultare passed toBacktrackas references, so every call works on the same objects. That is why the undo steps are needed: nothing is copied automatically. - The copy.
result.Add(current)would add the very same list object each time, and after the undo steps all the entries would be empty.new List<int>(current)makes an independent copy. continueends the current turn of theforloop and goes on with the nexti. Thei++still runs.CountandLength. AList<int>hasCount, an array hasLength. The solution uses both:current.Countandnums.Length.- Distinct numbers. The
usedarray tracks positions innums, not values. With repeated values, this code would produce duplicate permutations, which is why the lesson promises distinct numbers.
Complexity
Time: O(n · n!). There are n! permutations and copying each one takes n steps. The recursion has fewer than e · n! calls in all, and each call runs a loop over the n numbers, so the building work stays within the same O(n · n!) bound.
Space: O(n) beyond the answer. current and used hold at most n items, and the recursion is at most n + 1 calls deep. The answer itself holds n! lists, which is not counted as extra space. For eight numbers that is already 40,320 permutations.
Edge cases and common mistakes
- One number.
{1}gives[[1]]. - Two numbers.
{1, 2}gives[[1,2],[2,1]]. - The order follows
nums. For{3, 1, 2}the first permutation is[3,1,2], the array itself, and the last is its reverse,[2,1,3]. - Forgetting
used[i] = false. After the first permutation, every number stays marked and the search finds nothing more. - Forgetting the
RemoveAt.currentkeeps numbers from earlier branches, so the saved permutations are wrong. - Adding
currentwithout copying. Every entry in the result ends up empty.
Testing
{1, 2, 3}gives[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]].{3, 1, 2}gives[[3,1,2],[3,2,1],[1,3,2],[1,2,3],[2,3,1],[2,1,3]].{1}gives[[1]], and{1, 2}gives[[1,2],[2,1]].- Eight numbers give 40,320 permutations.
