46. Permutations
46. Permutations
Difficulty: Medium
Category: Backtracking, Recursion
Problem Statement
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.
Examples
Example 1:
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Example 2:
Input: nums = [0,1]
Output: [[0,1],[1,0]]
Example 3:
Input: nums = [1]
Output: [[1]]
Constraints
1 <= nums.length <= 6-10 <= nums[i] <= 10- All the integers of
numsare unique.
Approach
This is a classic backtracking problem that requires generating all possible permutations of a given array. There are two main approaches:
- Backtracking with Swapping: Generate permutations by swapping elements in-place
- STL next_permutation: Use C++ STL’s built-in permutation generator
Algorithm 1: Backtracking with Swapping
- Start from index 0 and try each element at current position
- Swap elements to place them at current position
- Recursively generate permutations for remaining positions
- Backtrack by swapping back to original positions
- Base case: When we’ve placed all elements, add current permutation
Algorithm 2: STL next_permutation
- Sort the array to get lexicographically smallest permutation
- Use do-while loop with
next_permutation()to generate all permutations - Add each permutation to result vector
- Continue until no more permutations exist
Solution
Solution 1: Backtracking with Swapping
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> rtn;
permute(nums, 0, rtn);
return rtn;
}
private:
void permute(vector<int>& nums, int idx, vector<vector<int>>& rtn) {
if(idx == nums.size()) {
rtn.push_back(nums);
return;
}
for(int i = idx; i < (int)nums.size(); i++) {
swap(nums[idx], nums[i]);
permute(nums, idx + 1, rtn);
swap(nums[idx], nums[i]);
}
}
};
Solution 2: STL next_permutation
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> rtn;
sort(nums.begin(), nums.end());
do{
rtn.push_back(nums);
} while(next_permutation(nums.begin(), nums.end()));
return rtn;
}
};
Explanation
Solution 1: Backtracking with Swapping
Step-by-Step Process:
- Base Case: When
idx == nums.size(), we’ve generated a complete permutation - Recursive Case: For each position
idx, try every element fromidxton-1 - Swap: Place element at position
ito positionidx - Recurse: Generate permutations for remaining positions (
idx + 1) - Backtrack: Swap back to restore original state
Example Walkthrough for nums = [1,2,3]:
Initial: [1,2,3], idx=0
├─ Swap(0,0): [1,2,3] → Recurse with idx=1
│ ├─ Swap(1,1): [1,2,3] → Recurse with idx=2
│ │ └─ Swap(2,2): [1,2,3] → idx=3, add [1,2,3]
│ └─ Swap(1,2): [1,3,2] → Recurse with idx=2
│ └─ Swap(2,2): [1,3,2] → idx=3, add [1,3,2]
├─ Swap(0,1): [2,1,3] → Recurse with idx=1
│ ├─ Swap(1,1): [2,1,3] → Recurse with idx=2
│ │ └─ Swap(2,2): [2,1,3] → idx=3, add [2,1,3]
│ └─ Swap(1,2): [2,3,1] → Recurse with idx=2
│ └─ Swap(2,2): [2,3,1] → idx=3, add [2,3,1]
└─ Swap(0,2): [3,2,1] → Recurse with idx=1
├─ Swap(1,1): [3,2,1] → Recurse with idx=2
│ └─ Swap(2,2): [3,2,1] → idx=3, add [3,2,1]
└─ Swap(1,2): [3,1,2] → Recurse with idx=2
└─ Swap(2,2): [3,1,2] → idx=3, add [3,1,2]
Solution 2: STL next_permutation
Step-by-Step Process:
- Sort array to get lexicographically smallest permutation
- Generate permutations using
next_permutation()in do-while loop - Add each permutation to result vector
- Continue until
next_permutation()returns false
Example Walkthrough for nums = [1,2,3]:
Sorted: [1,2,3]
1. [1,2,3] → Add to result
2. [1,3,2] → Add to result
3. [2,1,3] → Add to result
4. [2,3,1] → Add to result
5. [3,1,2] → Add to result
6. [3,2,1] → Add to result
7. next_permutation returns false → Stop
Complexity Analysis
Solution 1: Backtracking with Swapping
Time Complexity: O(n! × n)
- Permutations: n! permutations generated
- Each permutation: O(n) time to copy array
- Total: O(n! × n)
Space Complexity: O(n)
- Recursion depth: O(n) for the call stack
- No additional space for storing permutations during generation
Solution 2: STL next_permutation
Time Complexity: O(n! × n)
- Permutations: n! permutations generated
- Each permutation: O(n) time for
next_permutation()and copying - Total: O(n! × n)
Space Complexity: O(1)
- No recursion: Iterative approach
- No additional space beyond input and output
Key Insights
- Backtracking Pattern: Classic recursive approach with state restoration
- In-place Generation: Swapping allows generating permutations without extra space
- STL Efficiency:
next_permutation()is highly optimized - Lexicographic Order: STL approach generates permutations in sorted order
- State Management: Proper backtracking ensures all possibilities are explored
Comparison of Approaches
| Approach | Time | Space | Advantages | Disadvantages |
|---|---|---|---|---|
| Backtracking | O(n! × n) | O(n) | Educational, flexible | Recursive overhead |
| STL | O(n! × n) | O(1) | Concise, optimized | Less control over order |
Alternative Approaches
Approach 3: Backtracking with Visited Array
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> result;
vector<int> current;
vector<bool> used(nums.size(), false);
backtrack(nums, current, used, result);
return result;
}
private:
void backtrack(vector<int>& nums, vector<int>& current,
vector<bool>& used, 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, current, used, result);
current.pop_back();
used[i] = false;
}
}
};
Approach 4: Iterative with Stack
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> result;
stack<vector<int>> stk;
stk.push({});
while(!stk.empty()) {
vector<int> current = stk.top();
stk.pop();
if(current.size() == nums.size()) {
result.push_back(current);
continue;
}
for(int num : nums) {
if(find(current.begin(), current.end(), num) == current.end()) {
vector<int> next = current;
next.push_back(num);
stk.push(next);
}
}
}
return result;
}
};
When to Use Each Approach
Use Backtracking with Swapping when:
- Memory is limited (O(n) space vs O(n!) for visited approach)
- Need to understand the algorithm deeply
- Custom modifications are required
Use STL next_permutation when:
- Code simplicity is priority
- Lexicographic order is desired
- Performance is critical (highly optimized)
Use Visited Array when:
- Clarity is more important than space
- Need to track which elements are used
- Modifying the original array is not allowed
Key Concepts
- Permutations: All possible arrangements of elements
- Backtracking: Systematic exploration with state restoration
- Recursion: Divide problem into smaller subproblems
- State Space: All possible configurations to explore
- Pruning: Avoiding invalid or duplicate states
This problem is fundamental for understanding backtracking algorithms and is commonly used in interviews to test recursive thinking and state management skills.