Problem Statement
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.
A permutation is an arrangement of all or part of a set of objects, with regard to the order of the arrangement.
Additional information
1 <= nums.length <= 6-10 <= nums[i] <= 10- All the integers of
numsare unique.
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]]
Iterative Insertion (Brute Force)
Intuition
A highly intuitive, albeit brute-force, way to think about building permutations is by inserting numbers one by one into every possible position of previously built permutations.
Imagine we start with an empty permutation [[]].
When we process the first number 1, we can only place it in one spot: [[1]].
When we process the next number 2, we take all our existing permutations (just [1]) and insert 2 at every possible index (index 0 and index 1). This gives us [[2, 1], [1, 2]].
When we process 3, we take [2, 1] and insert 3 at index 0, 1, and 2. Then we take [1, 2] and insert 3 at index 0, 1, and 2.
We cascade this insertion process for every number in the array until we've built all complete permutations.
Algorithm
- Initialize a list of permutations
permswith an empty list inside:[[]]. - Iterate through each number
numin thenumsarray. - For each number, create a fresh empty list
new_permsto hold the permutations for this round. - Iterate through each existing permutation
pinperms. - Iterate through every possible insertion index
ifrom0tolen(p)(inclusive). - Create a copy of
p, insertnumat indexi, and append this newly formed list tonew_perms. - Update
perms = new_perms. - Return
perms.
def permute(nums: list[int]) -> list[list[int]]:
perms = [[]]
for num in nums:
new_perms = []
for p in perms:
# Insert the current number at every possible position
for i in range(len(p) + 1):
p_copy = p.copy()
p_copy.insert(i, num)
new_perms.append(p_copy)
perms = new_perms
return perms
Time & Space Complexity
Time complexity: O(N * N!)
Reason: For an array of length N, there are exactly N! (N-factorial) permutations. We must generate all of them. Constructing each permutation involves copying an array of up to size N, which makes the total time heavily bound by O(N * N!).
Space complexity: O(N * N!)
Reason: We must store all N! permutations to return them, and each permutation takes up O(N) space. The temporary
new_permsarray also requires this much space during the final iteration.
Recursive Backtracking (DFS) - Optimal
Intuition
We can build permutations much more elegantly using a Depth-First Search (DFS) decision tree.
At the very top of the tree, we have a blank permutation. We can choose any number from the input array to be our first element. Once we pick a number, we move down a level in the tree. Now, we can pick any number except the one we just used. We keep branching down until our current permutation is exactly as long as the input array.
Because all numbers in the input are unique, we can easily check if a number has already been used by seeing if it's currently inside our growing path. When we reach the bottom of the tree (a full permutation), we backtrack-we pop the last number off so we can go back up and explore a different branch.
Algorithm
- Initialize an empty list
resfor our final permutations. - Create a recursive helper function
dfs(current_perm). - Base Case: If the length of
current_permis equal to the length ofnums, we have a complete permutation. Append a.copy()of it toresand return. - Recursive Step: Loop through every
numinnums. - If
numis already insidecurrent_perm, skip it (we can't use the same number twice). - If it's available, append
numtocurrent_perm. - Recursively call
dfs(current_perm)to pick the next number. - Backtrack: Pop the last element off
current_permso the loop can move on and try placing the next available number in this slot instead. - Call
dfs([])to start and returnres.
def permute(nums: list[int]) -> list[list[int]]:
res = []
def dfs(current_perm):
# Base case: we have placed all numbers
if len(current_perm) == len(nums):
res.append(current_perm.copy())
return
for num in nums:
# Skip if the number is already used in the current path
if num in current_perm:
continue
# Include the number and explore
current_perm.append(num)
dfs(current_perm)
# Backtrack
current_perm.pop()
dfs([])
return res
Time & Space Complexity
Time complexity: O(N * N!)
Reason: The number of nodes in the backtracking decision tree is proportional to N!. At each leaf node (when a permutation is complete), we copy the array of size N, resulting in O(N * N!) total time. The
num in current_permcheck takes up to O(N) time, but because the maximum constraint for N is extremely small (N <= 6), this behaves like an O(1) operation.Space complexity: O(N)
Reason: Excluding the output array, the auxiliary space is strictly bounded by the maximum depth of the recursion stack and the
current_permlist, both of which never exceed a size of N.
Track
| Question | Difficulty | Company | Access |
|---|
PayPal