Hello! Let's dive into our next lesson on backtracking.
In our previous session, we mastered generating all subsets using a recursive "include/exclude" pattern. This was about choosing which elements belong in a group, where order didn't matter.
Today, we'll tackle a related but distinct problem: generating all permutations. Here, the goal is to find every possible ordering of a given set of elements. For example, for the set {1, 2, 3}, the permutations are [1, 2, 3], [1, 3, 2], [2, 1, 3], and so on. Unlike subsets, every element must be used exactly once, and their order is what defines a unique permutation.
This is another classic problem that frequently appears in technical interviews. By the end of this lesson, you will be able to generate all permutations of a set using a space-efficient, swap-based backtracking approach.
1. The Core Idea: Fix, Permute, and Swap
The problem of finding all permutations of n elements can be broken down recursively. The main idea is to:
- Fix an element for the first position.
- Permute the remaining
n-1elements. - Repeat this process by placing every available element into the first position.
How can we do this efficiently? Instead of using extra data structures to keep track of used elements (like a boolean array or a HashSet), we can perform the operation in-place within the array itself using swaps.
The strategy for an array nums at a given index is:
- Iterate from
i = indexto the end of the array. - In each iteration, swap the element at
indexwith the element ati. This effectively places thei-th element at theindex-th position. - After the swap, we have "fixed" the element at
index. We then make a recursive call to generate all permutations for the rest of the array, starting fromindex + 1. - Once the recursive call returns, we swap back the elements to restore the array to its original state. This is the crucial backtracking step, ensuring that the next iteration of the loop works with a clean slate.
Let's watch a video that provides an excellent visualization of this process.
L13. Print all Permutations of a String/Array | Recursion | Approach - 2
The video 'L13. Print all Permutations of a String/Array' from the 'take U forward' channel demonstrates this swap-based technique with a clear, step-by-step example.
Please watch from 00:34 to 13:30. Pay close attention to how the algorithm builds the recursion tree for the input [1, 2, 3]. Notice how each level of recursion corresponds to fixing an element at a specific position (index), and how the swaps explore all possibilities for that position.
This recursion tree illustrates the process beautifully. At each level, we decide which number gets to be in the current position, and then we recursively solve the problem for the positions that follow.

2. Implementation in Java: The Backtracking Pattern
Now, let's translate this logic into a Java implementation. The structure will feel familiar after our lesson on subsets, but the core logic inside the loop is different.
To understand the complete logic and the importance of each step, this article provides a clear, annotated code example.
Finding All Permutations Recursively in Java
The article 'Finding All Permutations Recursively in Java' by Alexander Obregon explains the swap-based method and the importance of backtracking.
Please read the sections 'The Core Recursive Idea', 'Why Backtracking Matters', and 'Collecting Results in a List'. Focus on how the swap operation is used before and after the recursive call, and how the final results are collected into a List<List<Integer>>.
Code Deconstruction
Here is a clean implementation based on the resources. This is the pattern you'll want to have ready for interviews.
import java.util.ArrayList;
import java.util.List;
import java.util.Collections; // For Collections.swap if using a List
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(0, nums, result);
return result;
}
private void backtrack(int index, int[] nums, List<List<Integer>> result) {
// Base Case: If the index reaches the end, we have a complete permutation.
if (index == nums.length) {
// Create a new list and copy the elements from the current state of `nums`.
List<Integer> currentPermutation = new ArrayList<>();
for (int num : nums) {
currentPermutation.add(num);
}
result.add(currentPermutation);
return;
}
// Recursive Step: Iterate through the elements from the current index to the end.
for (int i = index; i < nums.length; i++) {
// 1. Make a choice: Swap the current element `i` into the `index` position.
swap(nums, index, i);
// 2. Recurse: Explore permutations for the rest of the array.
backtrack(index + 1, nums, result);
// 3. Backtrack: Undo the choice by swapping back.
swap(nums, index, i);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
Key Points to Analyze:
-
indexParameter: This parameter tracks the current position we are trying to fill. The recursion depth is directly tied to this index. -
Base Case:
if (index == nums.length): When ourindexhas moved past the last element, it means we have successfully placed an element in every position from0ton-1. At this point, thenumsarray itself holds one complete permutation. We add a copy of it to our results. -
The Loop and Swap: The loop
for (int i = index; ...)is the engine of our exploration.swap(nums, index, i): This is the action. We are choosing thei-th element to be placed at theindex-th position.backtrack(index + 1, ...): This is the recursion. We trust that the function will handle all permutations for the subarray starting atindex + 1.swap(nums, index, i)(the second time): This is the backtrack. After returning from the recursion, we undo our swap. This is vital! It ensures that when the loop proceeds toi+1, the array is in the same state as it was before we decided to place thei-th element, allowing us to correctly place the(i+1)-th element at theindexposition next.
Test your understanding!
You are running permute on nums = [1, 2, 3]. The backtrack function is called with index = 0. The loop starts with i = 0. It swaps nums[0] with nums[0], so the array is still [1, 2, 3]. Then it calls backtrack(1, [1, 2, 3], result).
Inside this new recursive call (index = 1), what will be the first two values of i in its for loop, and what will the state of the nums array be just before the next recursive call in each of those two iterations?
Show answer
The loop runs for (int i = index; ...), so with index = 1, the loop will start with i = 1.
i = 1:swap(nums, 1, 1)is performed. The array remains[1, 2, 3]. The next recursive call will bebacktrack(2, [1, 2, 3], result).i = 2: After the first call returns and backtracks, the array is restored to[1, 2, 3]. The loop continues withi = 2.swap(nums, 1, 2)is performed. The array becomes[1, 3, 2]. The next recursive call will bebacktrack(2, [1, 3, 2], result).
3. A Critical Insight: Why does the loop start from index?
A common point of confusion is why the inner loop starts from i = index and not i = 0. If we started from i = 0 every time, we would generate redundant permutations. By starting from index, we ensure we only swap with elements that have not yet been fixed in the prefix 0...index-1.
This next video segment explains this exact point very well. Although the audio is in Hindi, the visual animation and code clearly demonstrate the logic.
Permutations | INTUITIVE | Backtracking Template | Two Ways | Similar Problems | Leetcode 46
The video 'Permutations | INTUITIVE | Backtracking Template' from 'codestorywithMIK' has a great section explaining why the swap loop should start from the current index.
Please watch from 22:18 to 28:00. Focus entirely on the visual animation on the screen. The instructor shows what happens if you try to swap from the beginning of the array (i=0) versus starting from the current index (i=idx). This visualization is key to building the right intuition.
4. Complexity Analysis
For any interview, you need to know the complexity. Given your background, this should be a straightforward analysis.
-
Time Complexity: O(N × N!)
- How many permutations are there for
Ndistinct elements? N! (N factorial). - The recursion tree has N! leaf nodes, and each leaf node corresponds to one complete permutation.
- At each leaf node, we perform an O(N) operation to copy the current state of the array into a new list to be added to our results.
- Thus, the total time complexity is dominated by this collection step, resulting in O(N × N!).
- How many permutations are there for
-
Space Complexity: O(N) (Auxiliary Space)
- The primary space used by the algorithm itself (excluding the output list) is the recursion call stack.
- The maximum depth of the recursion is
N. Therefore, the auxiliary space complexity is O(N). - If you are asked to include the space for the output, it would be O(N × N!), as we store N! permutations, each of length N. Always clarify this with your interviewer!
You can review this analysis in the take U forward video (LINK) from timestamp 13:30 to 14:30.
Conclusion
Today we've added a powerful and efficient pattern to your backtracking toolkit. The swap-based method for permutations is elegant because it avoids the overhead of extra space for tracking state, modifying the input array directly.
Key Takeaways:
- Permutations are about arrangement, while subsets are about selection.
- The core strategy is to fix a position, explore possibilities for it, and recurse on the remainder of the array.
- The
swap(index, i)followed bybacktrack(index + 1)and thenswap(index, i)again is the fundamental pattern for this problem. - Starting the exploration loop from
i = indexis crucial for avoiding duplicate work and generating correct, unique permutations. - Be ready to discuss the O(N × N!) time and O(N) auxiliary space complexity.
Preview of the Next Lesson:
We've covered subsets (order doesn't matter, any size) and permutations (order matters, size N). In the next lesson, we will look at combinations. This can be seen as a hybrid: we will generate all subsets of a specific size k. This will require us to adapt the backtracking logic we learned for subsets to handle the size constraint.