Loading problems...
TL;DR: The optimal solution uses backtracking to explore the decision tree of possible arrangements by swapping elements in-place to fix a number at the current index and recursively permuting the remaining portion.
The LeetCode 46 problem, titled Permutations, requires generating all possible orderings of a given array of distinct integers. Unlike combination problems where order does not matter, a permutation considers [1, 2] and [2, 1] as distinct results. Given the constraints (array length up to 6), the solution space is factorial in nature (), necessitating an exhaustive search strategy.
A naive attempt to solve this problem often involves trying to use iterative loops. For an array of length , one might attempt to write nested loops to pick the first element, then the second, and so on.
Pseudo-code for a fixed length of 3:
textresults = [] for i in nums: for j in nums: if j != i: for k in nums: if k != i and k != j: results.add([i, j, k])
Why this fails:
if k != i and k != j) becomes computationally expensive ( inside the innermost loop) as grows.The core insight for solving LeetCode 46 is to view the permutation process as a series of decisions: "Which number goes into index 0? Which remaining number goes into index 1?" and so on.
Instead of creating new arrays for every recursive call or maintaining a separate boolean "visited" array (which increases space complexity), we can maintain the state in-place within the input array.
The Invariant:
At any recursive step acting on index first, the subarray nums[0...first-1] is fixed and represents the current partial permutation. The subarray nums[first...n-1] contains all the candidates available to be placed at index first.
By swapping elements from the available pool (indices first to n-1) into the current position (first), we explore all possibilities for that specific slot. After the recursive call returns, we backtrack by swapping the element back to its original position. This restores the array to the state it was in before the decision, allowing the algorithm to explore the next candidate cleanly.
Visual Description:
Imagine a recursion tree. At the root (index 0), we have branches for every number in the array. If we pick 1 to be at index 0, we move down a branch where 1 is fixed. The next level (index 1) branches out for every remaining number (e.g., 2 and 3). This continues until we reach a leaf node where all positions are filled. The "backtracking" is the movement back up the tree branch to the previous node to take a different path.
![]()
Consider nums = [1, 2, 3].
backtrack(0):
nums[0] with nums[0]. Array is [1, 2, 3].backtrack(1):
nums[1] with nums[1]. Array is [1, 2, 3].backtrack(2):
nums[2] with nums[2]. Array is [1, 2, 3].backtrack(3): Base case met. Add [1, 2, 3]. Return.nums[2] with nums[2].nums[1] with nums[1].nums[1] with nums[2]. Array is [1, 3, 2].backtrack(2):
[1, 3, 2]).nums[0] with nums[0].nums[0] with nums[1]. Array is [2, 1, 3].The algorithm is correct because it exhaustively explores the state space defined by .
for i from first to n-1 ensures that every available number gets a chance to sit at the index first.cppclass Solution { public: vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> result; backtrack(nums, 0, result); return result; } private: void backtrack(vector<int>& nums, int start, vector<vector<int>>& result) { // Base case: if start index reaches the end, we have a complete permutation if (start == nums.size()) { result.push_back(nums); return; } // Iterate over all possible candidates for the current position 'start' for (int i = start; i < nums.size(); i++) { // Swap the current element with the candidate element swap(nums[start], nums[i]); // Recurse for the next position backtrack(nums, start + 1, result); // Backtrack: undo the swap to restore the original state swap(nums[start], nums[i]); } } };
javaimport java.util.ArrayList; import java.util.Collections; import java.util.List; class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> result = new ArrayList<>(); // Convert int[] to List<Integer> to facilitate swapping and storage List<Integer> numsList = new ArrayList<>(); for (int num : nums) { numsList.add(num); } backtrack(numsList, 0, result); return result; } private void backtrack(List<Integer> nums, int start, List<List<Integer>> result) { // Base case: if start index reaches the size, permutation is complete if (start == nums.size()) { result.add(new ArrayList<>(nums)); // Important: add a COPY return; } for (int i = start; i < nums.size(); i++) { // Swap current index with the loop index Collections.swap(nums, start, i); // Recurse down the decision tree backtrack(nums, start + 1, result); // Backtrack: swap back to restore state Collections.swap(nums, start, i); } } }
pythonclass Solution: def permute(self, nums: List[int]) -> List[List[int]]: result = [] def backtrack(start_index): # Base case: if start_index is at the end, we found a permutation if start_index == len(nums): result.append(nums[:]) # Important: append a COPY (slice) return for i in range(start_index, len(nums)): # Swap the current element with the candidate nums[start_index], nums[i] = nums[i], nums[start_index] # Recurse for the next index backtrack(start_index + 1) # Backtrack: undo the swap nums[start_index], nums[i] = nums[i], nums[start_index] backtrack(0) return result
Time Complexity:
Space Complexity:
visited array) are needed.The Backtracking - Permutations pattern is fundamental and appears in several variations:
Q: Why is my output filled with identical arrays?
nums directly to result, all entries in result will point to the same memory object. As the recursion continues modifying nums, all stored results change.new ArrayList<>(nums) or nums[:].Q: Why does my solution contain duplicates or miss permutations?
nums[start...end] contains the set of available candidates breaks.Q: Can I use if path contains current_num instead of swapping?
O(N)) to check if a number is already used in the current partial permutation.visited array).