mirror of
https://github.com/krahets/hello-algo.git
synced 2026-11-10 16:00:36 +08:00
* Retranslate the complete English edition from Simplified Chinese * Polish three English explanations after reader-focused review
96 lines
5.4 KiB
Markdown
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> 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.
|
|
|
|

|
|
|
|
### 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.
|
|
|
|

|
|
|
|
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.
|
|
|
|

|
|
|
|
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**.
|
|
|
|

|
|
|
|
### 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.
|
|
|
|

|