* Retranslate the complete English edition from Simplified Chinese * Polish three English explanations after reader-focused review
5.4 KiB
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.
Table Examples of permutations
| 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], setselected[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():
[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:
[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
selectedarray throughout the search. It records which elements are in the current state, preventing the same element from appearing twice instate. - Pruning equal elements: Each round of choices (each call to
backtrack) has its ownduplicatedset. It records the values already chosen in that round'sforloop, 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.