46. Permutations
Problem Description
Given an array nums
of distinct integers, the goal is to find all the possible permutations of these integers. A permutation is a rearrangement of the elements in an array in a different order. Since the array contains distinct integers, each permutation will also contain all the elements of the original array but in a different sequence. The result can be returned in any order, meaning that there is no need to sort or arrange the permutations in a particular sequence. The goal is to list all distinct ways the elements can be ordered.
Flowchart Walkthrough
To determine the appropriate algorithm for solving LeetCode problem 46, Permutations, we will utilize the Algorithm Flowchart. Here's a step-by-step analysis:
-
Is it a graph?
- No: The problem of finding permutations does not involve graph traversal or graph structures.
-
Need to solve for kth smallest/largest?
- No: The task is not focused on sorting or selecting a kth element.
-
Involves Linked Lists?
- No: The problem deals with arrays, with no linkages like those in linked lists.
-
Does the problem have small constraints?
- Yes: Permutations typically deal with relatively small sets of elements due to the factorial growth of possible permutations.
-
Brute force / Backtracking?
- Yes: Generating all permutations is a classic problem that benefits from a backtracking approach, as it involves exploring all possible configurations and reverting when a path does not lead to a valid permutation.
Conclusion: Based on the flowchart analysis, using a backtracking approach is recommended for efficiently generating all permutations of the array. This technique allows us to build permutations incrementally and backtrack when necessary.
Intuition
The intuition behind the solution is to use Depth-First Search (DFS). DFS is a common algorithm for traversing or searching tree or graph data structures. The idea is to follow one branch of the tree down as many levels as possible until the end is reached, and then proceed to the next branch.
When dealing with permutations, we can imagine each permutation as a path in a decision tree, where each level represents an element in the permutation and each branch represents a choice of which element to place next.
Here's how we can visualize this approach:
- Start with an empty permutation.
- For every index in the permutation, try placing each unused element (one that has not been used in this particular path/branch).
- After an element is placed at the current index, mark it as used and move to the next index.
- Once an index is placed with all possible elements, backtrack (step back) and try the next possible element for the previous index.
- Repeat until all elements are placed in the permutation, meaning we've reached the end of the branch. This permutation is then added to the list of results.
- When all branches are explored, we will have found all the permutations.
The dfs()
function is a recursive method that implements this approach. It uses the index i
to keep track of the depth of recursion (which corresponds to the current position in the permutation). The vis
array helps track which elements have been used, and the t
array represents the current permutation being constructed. Once index i
reaches n
, the length of nums
, it means a full permutation has been created and it's added to the answer ans
.
To sum up, the problem is solved by systematically exploring all possible placements of elements using DFS, while making sure to backtrack at the appropriate times, hence constructing all unique permutations.
Learn more about Backtracking patterns.
Solution Approach
The solution uses a classic Depth-First Search (DFS) algorithm implemented through recursion to explore all potential permutations in a systematic way. The key to the DFS algorithm in this context is to treat each level of recursion as a position in the permutation, and to attempt to fill that position with each possible unused element from the nums
array.
Here's a step-by-step breakdown of the algorithm:
-
Initialize an
n
-length boolean arrayvis
to keep track of which elements fromnums
have been used in the current branch of the DFS. At first, all values areFalse
indicating no elements have been used yet. -
Create a temporary array
t
of lengthn
. This array will hold the current permutation as it is being built. -
An empty list
ans
is initialized to store all the completed permutations. -
The
dfs
function is defined to perform the DFS. It takes an argumenti
which is the current depth of the DFS, corresponding to the index in the permutation where we are placing the next element. -
In the
dfs
function, the base condition checks ifi
is equal ton
. If true, it means we've reached the end of a branch in the DFS and a full permutation has been constructed, so we append a copy of this permutation toans
. -
If the base condition is not met, the function proceeds to iterate over all elements of
nums
.- For each element, if it has not already been used (i.e.,
vis[j]
isFalse
), we mark it as used by settingvis[j]
toTrue
. - We then place this element in the
i
-th position of the current permutation being builtt[i]
. - Call
dfs(i + 1)
to proceed to the next level of depth, attempting to find an element for the next position. - After returning from the deeper recursive call, we reset
vis[j]
toFalse
to "unchoose" the element, thus enabling it to be used in a different branch or permutation. This is the backtracking step.
- For each element, if it has not already been used (i.e.,
-
The initial call to
dfs
is made with an argument of0
to start the DFS from the first position in the permutation. -
Finally, the
ans
list is returned which now contains all possible permutations.
This approach uses a combination of recursive DFS for the traversal mechanism, backtracking for generating permutations without duplicates, and dynamic data structures to keep track of the used elements and current permutations. The elegance of this solution lies in how it natively handles the permutations' creation without redundancy, which is essential in problems where the order of elements matters and you want to consider all possible orderings.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using a simple example where our array nums
is [1,2,3]
. We want to find all permutations of this array.
-
We initialize a boolean array
vis
of length 3 (n=3
in this case), all set toFalse
, which will help us keep track of the elements that have been used in the current permutation. We also create a temporary arrayt
to build the current permutation and an empty listans
to store all permutations.vis
= [False, False, False]t
= [0, 0, 0]ans
= [] -
Start the DFS with
dfs(i=0)
. At this level of recursion, we are looking to fill in the first position of thet
array. -
Our
for
loop in thedfs
function will consider each element innums
: a. On the first iteration, we choose1
. We setvis[0]
toTrue
and place1
int[0]
. b. We calldfs(i=1)
to decide on the second element of the permutation. -
Now we are in a new level of recursion, trying to fill
t[1]
. Again, we traversenums
. We skip1
becausevis[0]
isTrue
. We pick2
, setvis[1]
toTrue
, and place2
int[1]
. a. We calldfs(i=2)
. -
The next level of recursion is to place the third element.
1
and2
are marked as used, so we pick3
, setvis[2]
toTrue
, and place3
int[2]
. -
Now,
i
equals ton
. We've reached the end of the branch and have a complete permutation[1,2,3]
, which we add toans
. -
We backtrack by returning to where we picked
3
. We unmarkvis[2]
(vis[2]=False
), trying to explore other possibilities for this position, but there are no more elements left to use. So, we backtrack further. -
Back at the second element decision step (
dfs(i=1)
), we backtrack off of element2
and pick3
fort[1]
.vis
is now[True, False, True]
. We calldfs(i=2)
to decide the third element. -
In this call,
2
is the only unused element, so we put it int[2]
, making the permutation[1,3,2]
. We add this toans
.
This recursive process continues, systematically exploring each possible permutation and backtracking after exploring each branch to the fullest extent. This ensures that we explore all permutations without duplication.
In the end, ans
would be:
ans = [ [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] ]
This walk through represents how the algorithm builds up permutations and how it builds the result step by step.
Solution Implementation
1class Solution:
2 def permute(self, nums: List[int]) -> List[List[int]]:
3 # Helper function to perform depth-first search for permutation generation
4 def backtrack(index):
5 # If the current index has reached the length of nums list,
6 # we have a complete permutation
7 if index == len_nums:
8 permutations.append(current_permutation[:])
9 return
10
11 # Iterate over the nums list to create permutations
12 for j in range(len_nums):
13 # Check if the number at index j is already used in the current permutation
14 if not visited[j]:
15 # If not visited, mark it as visited and add to current permutation
16 visited[j] = True
17 current_permutation[index] = nums[j]
18 # Recurse with next index
19 backtrack(index + 1)
20 # Backtrack: unmark the number at index j as visited for the next iteration
21 visited[j] = False
22
23 len_nums = len(nums) # Store the length of the input list
24 visited = [False] * len_nums # Create a visited list to track numbers that are used
25 current_permutation = [0] * len_nums # Temp list to store the current permutation
26 permutations = [] # Result list to store all the permutations
27 backtrack(0) # Start generating permutations from index 0
28 return permutations
29
1class Solution {
2 // List to hold all the permutations
3 private List<List<Integer>> permutations = new ArrayList<>();
4
5 // Temporary list to hold the current permutation
6 private List<Integer> currentPermutation = new ArrayList<>();
7
8 // Visited array to keep track of the elements already included in the permutation
9 private boolean[] visited;
10
11 // Array of numbers to create permutations from
12 private int[] elements;
13
14 // Method to initiate the process of finding all permutations
15 public List<List<Integer>> permute(int[] nums) {
16 elements = nums;
17 visited = new boolean[nums.length];
18 backtrack(0);
19 return permutations;
20 }
21
22 // Helper method to perform backtracking
23 private void backtrack(int index) {
24 // Base case: if the permutation size is equal to the number of elements, add it to the answer
25 if (index == elements.length) {
26 permutations.add(new ArrayList<>(currentPermutation));
27 return;
28 }
29
30 // Iterate through the elements array
31 for (int j = 0; j < elements.length; ++j) {
32 // If the element at index j has not been visited, include it in the permutation
33 if (!visited[j]) {
34 // Mark the element at index j as visited
35 visited[j] = true;
36 // Add the element to the current permutation
37 currentPermutation.add(elements[j]);
38 // Continue to the next level of depth (next index)
39 backtrack(index + 1);
40 // Backtrack: remove the last element added and mark it as not visited
41 currentPermutation.remove(currentPermutation.size() - 1);
42 visited[j] = false;
43 }
44 }
45 }
46}
47
1#include <vector>
2#include <functional> // For the std::function
3
4class Solution {
5public:
6 // Function to generate all permutations of the input vector of integers.
7 std::vector<std::vector<int>> permute(std::vector<int>& nums) {
8 int n = nums.size(); // Get the size of the nums vector.
9 std::vector<std::vector<int>> permutations; // To store all permutations.
10 std::vector<int> current_permutation(n); // Current permutation vector.
11 std::vector<bool> visited(n, false); // Visited flags for nums elements.
12
13 // Recursive Depth-First Search (DFS) function to generate permutations.
14 std::function<void(int)> dfs = [&](int depth) {
15 if (depth == n) { // Base case: if the current permutation is complete.
16 permutations.emplace_back(current_permutation); // Add to permutations.
17 return; // End of branch.
18 }
19 for (int i = 0; i < n; ++i) { // Iterate through nums elements.
20 if (!visited[i]) { // If the ith element has not been visited.
21 visited[i] = true; // Mark as visited.
22 current_permutation[depth] = nums[i]; // Set in current permutation.
23
24 dfs(depth + 1); // Recurse with the next depth.
25
26 visited[i] = false; // Unmark as visited for backtracking.
27 }
28 }
29 };
30
31 dfs(0); // Start the DFS with depth 0.
32 return permutations; // Return all the generated permutations.
33 }
34};
35
1// Function to generate all permutations of an array of numbers
2function permute(nums: number[]): number[][] {
3 const n = nums.length; // Length of the array to permute
4 const results: number[][] = []; // Results array that will hold all permutations
5
6 // Helper function 'depthFirstSearch' to explore the permutations using DFS strategy
7 const depthFirstSearch = (currentIndex: number) => {
8 // If the current index reaches the end of array, record the permutation
9 if (currentIndex === n) {
10 results.push([...nums]); // Add a copy of the current permutation
11 return;
12 }
13
14 // Iterate over the array to swap each element with the element at 'currentIndex'
15 for (let swapIndex = currentIndex; swapIndex < n; swapIndex++) {
16 // Swap the elements
17 [nums[currentIndex], nums[swapIndex]] = [nums[swapIndex], nums[currentIndex]];
18 // Recursively call 'depthFirstSearch' with the next index
19 depthFirstSearch(currentIndex + 1);
20 // Swap back the elements to revert to the original array before the next iteration
21 [nums[currentIndex], nums[swapIndex]] = [nums[swapIndex], nums[currentIndex]];
22 }
23 };
24
25 // Initiate depth-first search starting from index 0
26 depthFirstSearch(0);
27 // Return all generated permutations
28 return results;
29}
30
Time and Space Complexity
The provided Python code generates all permutations of a list of integers, using a backtracking algorithm.
Time Complexity
The time complexity of the algorithm is determined by the number of recursive calls made, and the work done in each call. The function dfs
is called recursively until it reaches the base case (i == n
).
For n
distinct elements, there are n!
(factorial of n
) permutations. At each level of the recursion, we make n
choices, then n - 1
for the next level, and so on, which means we are doing n!
work as there are that many permutations to generate and for each of them we do O(1)
operation. Hence, the time complexity is O(n!)
.
Space Complexity
The space complexity consists of the space used by the recursive call stack and the space used to maintain the state (visited array vis
and temporary list t
).
- Recursive Call Stack: Since the depth of the recursion is
n
, at mostO(n)
functions will be placed on the call stack simultaneously. - State Maintenance: The list
vis
andt
requireO(n)
space each.
The total space complexity, therefore, is O(n) + O(n) * O(2) = O(n)
. However, since O(2)
is a constant factor, it simplifies to O(n)
.
Taking all this into account, the space complexity is O(n)
for maintaining the auxiliary data structure and the recursive call stack depth.
Learn more about how to find time and space complexity quickly using problem constraints.
How does merge sort divide the problem into subproblems?
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!