Problem overview
The function permute receives a vector<int> named nums of distinct integers and returns every permutation of it: every way to put all of its numbers in a row, each exactly once, as a vector<vector<int>>.
The order of the answer is fixed. The positions are filled from first to last, and the numbers are always tried in their order in nums. All permutations that start with nums[0] come first, then those that start with nums[1], and so on, and inside a group the second number decides the same way. So nums itself is always first and nums reversed is last.
For nums = {1, 2, 3} the answer is:
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
For nums = {3, 1, 2} the numbers are tried in the order 3, 1, 2, not in ascending order, so the answer is:
[[3,1,2],[3,2,1],[1,3,2],[1,2,3],[2,3,1],[2,1,3]]
A single number, {1}, has one permutation: [[1]].
Approach: fill one position at a time
With n distinct numbers there are n! permutations: n choices for the first position, n - 1 for the second, and so on. Any solution has to produce all of them, so the goal is to generate each one exactly once, in the right order, without wasted work.
Backtracking builds a permutation one position at a time in a vector current. A second vector, used, holds one true-or-false value per number and says which numbers current already contains. To fill the next position:
- go through the numbers in order, from index 0;
- skip the used ones;
- for each free number, take it (mark it used and append it to
current), fill the remaining positions with a recursive call, then give it back (remove it and mark it free) so that the next number can try the same position.
When current holds n numbers, it is a whole permutation, and a copy goes into the result. Because the loop always tries the numbers in their order in nums, the permutations come out in exactly the order the statement describes.
Step by step
- Create
result, an emptyvector<vector<int>>. - Create
usedasvector<bool> used(nums.size(), false): the two arguments are the size and the value of every element.used[i]says whethernums[i]is already in the permutation being built. - Create
current, an emptyvector<int>. No position is filled yet. - Start the search with
backtrack(nums, used, current, result). Every number is still free. - Return
result, which the helper has filled. - Declare the helper
backtrackinside the class, returningvoid, with all four vectors passed by reference, because it changes three of them and the caller must see the changes. - Check whether
currentis full:current.size() == nums.size(). A fullcurrentholds every number once. - Save and return:
push_back(current)stores a copy inresult. - Loop over every index
ifrom 0 tonums.size() - 1. Starting at 0 every time is what gives the required order. - Skip used numbers: if
used[i]is true,continue. A number can't appear twice. - Mark it: set
used[i]totrue, so deeper calls won't take it again. - Append it:
current.push_back(nums[i])fills the next position. - Recurse to fill the remaining positions. This call finds every permutation that starts with what
currentnow holds. - Undo the append with
current.pop_back(). - Undo the mark: set
used[i]back tofalse, so permutations built later can usenums[i]in another position.
The solution in C++
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> result;
vector<bool> used(nums.size(), false);
vector<int> current;
backtrack(nums, used, current, result);
return result;
}
void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& current, vector<vector<int>>& result) {
if (current.size() == nums.size()) {
result.push_back(current);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i]) {
continue;
}
used[i] = true;
current.push_back(nums[i]);
backtrack(nums, used, current, result);
current.pop_back();
used[i] = false;
}
}
};
The C++ details worth a closer look:
vector<bool>is special. The standard library packs it into single bits, soused[i]returns a small proxy object instead of abool&. Reading it in anifand assigningtrueorfalseto it work as expected, which is all this code does. You can't take a normalbool&from it, though.- The constructor
vector<bool> used(nums.size(), false)buildsnums.size()elements, eachfalse. Writingvector<bool> used(nums.size())would also givefalseeverywhere, but spelling it out shows the intent. - References.
nums,used,currentandresultall travel by reference. Passingusedorcurrentby value would copy them at every call, and passingresultby value would leave the caller's result empty. push_back(current)copies the vector, so the saved permutation isn't affected whencurrentchanges afterwards.current.size() == nums.size()compares two unsigned values, so there is no sign mix. The loop'si < nums.size()compares anintwith an unsigned value: that is safe whileiis non-negative, and some compilers warn about it.continueskips the rest of the loop body for the currentiand moves on toi + 1. Thei++in the loop header still runs.
Complexity
Time: O(n · n!). There are n! permutations, and copying each into the result takes n steps. The recursion makes fewer than e · n! calls in all, and each call loops over the n numbers, so the work for the whole search stays within a constant factor of n · n!.
Space: O(n) extra. Besides the answer, the program holds current, used and one call per position, each of size at most n.
Edge cases and common mistakes
- Forgetting to unmark
used[i]. After the first permutation every number stays marked, so nothing else can be built. For{1, 2, 3}the function returns only[[1,2,3]]. - Forgetting
pop_back().currentstays full after the first permutation, so its size never equalsnums.size()again. For{1, 2, 3}the function again returns only[[1,2,3]]. - Looping from the wrong index. Starting the loop at a later index, as subset-style searches do, skips the numbers before it. A permutation can use any free number in any position, so the loop always starts at 0.
- Sorting
numsfirst. The statement asks for the numbers in their order innums. For{3, 1, 2}the answer starts with[3,1,2], not[1,2,3]. - One number.
{1}gives[[1]]. - Two numbers.
{-10, 10}gives[[-10,10],[10,-10]]. - The biggest input. With 8 numbers the answer has 40,320 permutations. For
{1, 2, 3, 4, 5, 6, 7, 8}the first is[1,2,3,4,5,6,7,8]and the last is[8,7,6,5,4,3,2,1].
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]].nums = {-10, 10}gives[[-10,10],[10,-10]].nums = {1, 2, 3, 4, 5, 6, 7, 8}gives 40,320 permutations, from[1,2,3,4,5,6,7,8]to[8,7,6,5,4,3,2,1].
