Files
hello-algo/en/docs/chapter_backtracking/permutations_problem.md
Yudong Jin 14c314c08c Retranslate the complete English edition from Simplified Chinese (#2010)
* Retranslate the complete English edition from Simplified Chinese

* Polish three English explanations after reader-focused review
2026-10-31 03:35:51 +08:00

96 lines
5.4 KiB
Markdown

# Permutations problem
Generating permutations is a classic application of backtracking. Given a collection, such as an array or a string, the task is to find every possible ordering of its elements.
The table below gives several input arrays and all their permutations.
<p align="center"> Table <id> &nbsp; Examples of permutations </p>
| Input array | All permutations |
| :---------- | :----------------------------------------------------------------- |
| $[1]$ | $[1]$ |
| $[1, 2]$ | $[1, 2], [2, 1]$ |
| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ |
## Distinct elements
!!! question
Given an integer array with no duplicate elements, return all possible permutations.
From a backtracking perspective, **we can view a permutation as the result of a sequence of choices**. For input $[1, 2, 3]$, choosing $1$, then $3$, then $2$ produces $[1, 3, 2]$. Backtracking undoes a choice so that other choices can be tried.
In the code, `choices` contains all the input elements, and `state` contains the elements chosen so far. Each element may be chosen only once, **so every element in `state` must be unique**.
As shown in the figure below, we can expand the search into a recursion tree, where each node represents the current `state`. Starting from the root, three rounds of choices lead to a leaf, and each leaf corresponds to one permutation.
![Recursion tree for permutations](permutations_problem.assets/permutations_i.png)
### Pruning repeated selections
To ensure that each element is chosen only once, introduce a Boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. Use it for the following pruning steps.
- After choosing `choices[i]`, set `selected[i]` to $\text{True}$ to mark it as selected.
- When iterating over `choices`, skip all selected elements, pruning their branches.
As shown in the figure below, if we choose 1 first, 3 second, and 2 third, we must prune the branch for 1 in the second round and the branches for 1 and 3 in the third round.
![Example of pruning permutations](permutations_problem.assets/permutations_i_pruning.png)
This pruning reduces the search space from $O(n^n)$ to $O(n!)$.
### Code implementation
With these details established, we can fill in the backtracking template. To keep the code short, we inline the template's helper functions into `backtrack()`:
```src
[file]{permutations_i}-[class]{}-[func]{permutations_i}
```
## Duplicate elements
!!! question
Given an integer array that **may contain duplicate elements**, return all distinct permutations.
Suppose the input is $[1, 1, 2]$. To distinguish the two occurrences of $1$, denote the second $1$ by $\hat{1}$.
As shown in the figure below, half the permutations generated by the previous method are duplicates.
![Duplicate permutations](permutations_problem.assets/permutations_ii.png)
How can we remove these duplicates? A straightforward approach is to use a hash set to deduplicate the results. However, this is inelegant: **branches that generate duplicate permutations are unnecessary and should be identified and pruned in advance**, improving efficiency.
### Pruning equal elements
In the figure below, choosing $1$ or $\hat{1}$ in the first round is equivalent: every permutation produced under one choice is duplicated under the other. We should therefore prune $\hat{1}$.
Similarly, after choosing $2$ in the first round, choosing either $1$ or $\hat{1}$ in the second produces duplicate branches. Prune the second-round choice of $\hat{1}$ as well.
In essence, **within any one round of choices, we want to try only one of the elements with the same value**.
![Pruning duplicate permutations](permutations_problem.assets/permutations_ii_pruning.png)
### Code implementation
Building on the previous solution, create a hash set `duplicated` in each round to record the values already tried in that round and prune repeated values:
```src
[file]{permutations_ii}-[class]{}-[func]{permutations_ii}
```
If all elements are distinct, $n$ elements have $n!$ permutations, where the exclamation mark denotes factorial. Recording each result requires copying a list of length $n$, taking $O(n)$ time. **The time complexity is therefore $O(n!n)$**.
The maximum recursion depth is $n$, requiring $O(n)$ space for stack frames. The array `selected` uses $O(n)$ space. At most $n$ `duplicated` sets exist at once, using $O(n^2)$ space. **The space complexity is therefore $O(n^2)$**.
### Comparing the two pruning methods
Although both `selected` and `duplicated` are used for pruning, they serve different purposes.
- **Pruning repeated selections**: There is only one `selected` array throughout the search. It records which elements are in the current state, preventing the same element from appearing twice in `state`.
- **Pruning equal elements**: Each round of choices (each call to `backtrack`) has its own `duplicated` set. It records the values already chosen in that round's `for` loop, ensuring that equal elements are tried only once in that round.
The figure below shows the scope of each pruning condition. Each node represents a choice, and the nodes along a path from root to leaf form a permutation.
![Scope of the two pruning conditions](permutations_problem.assets/permutations_ii_pruning_summary.png)