--- comments: true --- # 13.2   Permutations Problem The permutations problem is a classic application of backtracking algorithms. It is defined as finding all possible arrangements of elements in a given collection (such as an array or string). Table 13-2 shows several example datasets, including input arrays and their corresponding permutations.

Table 13-2   Permutations Examples

| 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]$ |
## 13.2.1   Case with Distinct Elements !!! question Given an integer array with no duplicate elements, return all possible permutations. From the perspective of backtracking algorithms, **we can imagine the process of generating permutations as the result of a series of choices**. Suppose the input array is $[1, 2, 3]$. If we first choose $1$, then choose $3$, and finally choose $2$, we obtain the permutation $[1, 3, 2]$. Backtracking means undoing a choice and then trying other choices. From the perspective of backtracking code, the candidate set `choices` consists of all elements in the input array, and the state `state` is the elements that have been chosen so far. Note that each element can only be chosen once, **therefore all elements in `state` should be unique**. As shown in Figure 13-5, we can unfold the search process into a recursion tree, where each node in the tree represents the current state `state`. Starting from the root node, after three rounds of choices, we reach a leaf node, and each leaf node corresponds to a permutation. ![Recursion tree of permutations](permutations_problem.assets/permutations_i.png){ class="animation-figure" }

Figure 13-5   Recursion tree of permutations

### 1.   Pruning Duplicate Choices To ensure that each element is chosen only once, we consider introducing a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We implement the following pruning operation based on it. - After making a choice `choices[i]`, we set `selected[i]` to $\text{True}$, indicating that it has been chosen. - When traversing the candidate list `choices`, we skip all nodes that have been chosen, which is pruning. As shown in Figure 13-6, suppose we choose $1$ in the first round, $3$ in the second round, and $2$ in the third round. Then we need to prune the branch of element $1$ in the second round and prune the branches of elements $1$ and $3$ in the third round. ![Pruning example of permutations](permutations_problem.assets/permutations_i_pruning.png){ class="animation-figure" }

Figure 13-6   Pruning example of permutations

Observing the above figure, we find that this pruning operation reduces the search space size from $O(n^n)$ to $O(n!)$. ### 2.   Code Implementation After understanding the above information, we can fill in the blanks in the template code. To shorten the overall code, we do not implement each function in the template separately, but instead unfold them in the `backtrack()` function: === "Python" ```python title="permutations_i.py" def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): """Backtracking algorithm: Permutations I""" # When the state length equals the number of elements, record the solution if len(state) == len(choices): res.append(list(state)) return # Traverse all choices for i, choice in enumerate(choices): # Pruning: do not allow repeated selection of elements if not selected[i]: # Attempt: make choice, update state selected[i] = True state.append(choice) # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = False state.pop() def permutations_i(nums: list[int]) -> list[list[int]]: """Permutations I""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res ``` === "C++" ```cpp title="permutations_i.cpp" /* Backtracking algorithm: Permutations I */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.size()) { res.push_back(state); return; } // Traverse all choices for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push_back(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop_back(); } } } /* Permutations I */ vector> permutationsI(vector nums) { vector state; vector selected(nums.size(), false); vector> res; backtrack(state, nums, selected, res); return res; } ``` === "Java" ```java title="permutations_i.java" /* Backtracking algorithm: Permutations I */ void backtrack(List state, int[] choices, boolean[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.remove(state.size() - 1); } } } /* Permutations I */ List> permutationsI(int[] nums) { List> res = new ArrayList>(); backtrack(new ArrayList(), nums, new boolean[nums.length], res); return res; } ``` === "C#" ```csharp title="permutations_i.cs" /* Backtracking algorithm: Permutations I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Traverse all choices for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.Add(choice); // Proceed to the next round of selection Backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Permutations I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } ``` === "Go" ```go title="permutations_i.go" /* Backtracking algorithm: Permutations I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // When the state length equals the number of elements, record the solution if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Traverse all choices for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Pruning: do not allow repeated selection of elements if !(*selected)[i] { // Attempt: make choice, update state (*selected)[i] = true *state = append(*state, choice) // Proceed to the next round of selection backtrackI(state, choices, selected, res) // Backtrack: undo choice, restore to previous state (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Permutations I */ func permutationsI(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackI(&state, &nums, &selected, &res) return res } ``` === "Swift" ```swift title="permutations_i.swift" /* Backtracking algorithm: Permutations I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // When the state length equals the number of elements, record the solution if state.count == choices.count { res.append(state) return } // Traverse all choices for (i, choice) in choices.enumerated() { // Pruning: do not allow repeated selection of elements if !selected[i] { // Attempt: make choice, update state selected[i] = true state.append(choice) // Proceed to the next round of selection backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeLast() } } } /* Permutations I */ func permutationsI(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } ``` === "JS" ```javascript title="permutations_i.js" /* Backtracking algorithm: Permutations I */ function backtrack(state, choices, selected, res) { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "TS" ```typescript title="permutations_i.ts" /* Backtracking algorithm: Permutations I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations I */ function permutationsI(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "Dart" ```dart title="permutations_i.dart" /* Backtracking algorithm: Permutations I */ void backtrack( List state, List choices, List selected, List> res, ) { // When the state length equals the number of elements, record the solution if (state.length == choices.length) { res.add(List.from(state)); return; } // Traverse all choices for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.removeLast(); } } } /* Permutations I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } ``` === "Rust" ```rust title="permutations_i.rs" /* Backtracking algorithm: Permutations I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // When the state length equals the number of elements, record the solution if state.len() == choices.len() { res.push(state); return; } // Traverse all choices for i in 0..choices.len() { let choice = choices[i]; // Pruning: do not allow repeated selection of elements if !selected[i] { // Attempt: make choice, update state selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state.clone(), choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } } } /* Permutations I */ fn permutations_i(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); // State (subset) backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } ``` === "C" ```c title="permutations_i.c" /* Backtracking algorithm: Permutations I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // When the state length equals the number of elements, record the solution if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Traverse all choices for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true; state[stateSize] = choice; // Proceed to the next round of selection backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Backtrack: undo choice, restore to previous state selected[i] = false; } } } /* Permutations I */ int **permutationsI(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } ``` === "Kotlin" ```kotlin title="permutations_i.kt" /* Backtracking algorithm: Permutations I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // When the state length equals the number of elements, record the solution if (state.size == choices.size) { res.add(state.toMutableList()) return } // Traverse all choices for (i in choices.indices) { val choice = choices[i] // Pruning: do not allow repeated selection of elements if (!selected[i]) { // Attempt: make choice, update state selected[i] = true state.add(choice) // Proceed to the next round of selection backtrack(state, choices, selected, res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeAt(state.size - 1) } } } /* Permutations I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } ``` === "Ruby" ```ruby title="permutations_i.rb" ### Backtracking: permutations I ### def backtrack(state, choices, selected, res) # When the state length equals the number of elements, record the solution if state.length == choices.length res << state.dup return end # Traverse all choices choices.each_with_index do |choice, i| # Pruning: do not allow repeated selection of elements unless selected[i] # Attempt: make choice, update state selected[i] = true state << choice # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = false state.pop end end end ### Permutations I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ``` ## 13.2.2   Case with Duplicate Elements !!! question Given an integer array that **may contain duplicate elements**, return all unique permutations. Suppose the input array is $[1, 1, 2]$. To distinguish the two duplicate elements $1$, we denote the second $1$ as $\hat{1}$. As shown in Figure 13-7, half of the permutations generated by the above method are duplicates. ![Duplicate permutations](permutations_problem.assets/permutations_ii.png){ class="animation-figure" }

Figure 13-7   Duplicate permutations

So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because **the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early**, which can further improve algorithm efficiency. ### 1.   Pruning Equal Elements Observe Figure 13-8. In the first round, choosing $1$ or choosing $\hat{1}$ is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune $\hat{1}$. Similarly, after choosing $2$ in the first round, the $1$ and $\hat{1}$ in the second round also produce duplicate branches, so the second round's $\hat{1}$ should also be pruned. Essentially, **our goal is to ensure that multiple equal elements are chosen only once in a certain round of choices**. ![Pruning duplicate permutations](permutations_problem.assets/permutations_ii_pruning.png){ class="animation-figure" }

Figure 13-8   Pruning duplicate permutations

### 2.   Code Implementation Building on the code from the previous problem, we initialize a hash set `duplicated` in each round of choices to record which elements have already been tried in that round, and prune equal elements: === "Python" ```python title="permutations_ii.py" def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): """Backtracking algorithm: Permutations II""" # When the state length equals the number of elements, record the solution if len(state) == len(choices): res.append(list(state)) return # Traverse all choices duplicated = set[int]() for i, choice in enumerate(choices): # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if not selected[i] and choice not in duplicated: # Attempt: make choice, update state duplicated.add(choice) # Record the selected element value selected[i] = True state.append(choice) # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = False state.pop() def permutations_ii(nums: list[int]) -> list[list[int]]: """Permutations II""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res ``` === "C++" ```cpp title="permutations_ii.cpp" /* Backtracking algorithm: Permutations II */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.size()) { res.push_back(state); return; } // Traverse all choices unordered_set duplicated; for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && duplicated.find(choice) == duplicated.end()) { // Attempt: make choice, update state duplicated.emplace(choice); // Record the selected element value selected[i] = true; state.push_back(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop_back(); } } } /* Permutations II */ vector> permutationsII(vector nums) { vector state; vector selected(nums.size(), false); vector> res; backtrack(state, nums, selected, res); return res; } ``` === "Java" ```java title="permutations_ii.java" /* Backtracking algorithm: Permutations II */ void backtrack(List state, int[] choices, boolean[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Traverse all choices Set duplicated = new HashSet(); for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.remove(state.size() - 1); } } } /* Permutations II */ List> permutationsII(int[] nums) { List> res = new ArrayList>(); backtrack(new ArrayList(), nums, new boolean[nums.length], res); return res; } ``` === "C#" ```csharp title="permutations_ii.cs" /* Backtracking algorithm: Permutations II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // When the state length equals the number of elements, record the solution if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Traverse all choices HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.Contains(choice)) { // Attempt: make choice, update state duplicated.Add(choice); // Record the selected element value selected[i] = true; state.Add(choice); // Proceed to the next round of selection Backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Permutations II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } ``` === "Go" ```go title="permutations_ii.go" /* Backtracking algorithm: Permutations II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // When the state length equals the number of elements, record the solution if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Traverse all choices duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // Attempt: make choice, update state // Record the selected element value duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // Proceed to the next round of selection backtrackII(state, choices, selected, res) // Backtrack: undo choice, restore to previous state (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Permutations II */ func permutationsII(nums []int) [][]int { res := make([][]int, 0) state := make([]int, 0) selected := make([]bool, len(nums)) backtrackII(&state, &nums, &selected, &res) return res } ``` === "Swift" ```swift title="permutations_ii.swift" /* Backtracking algorithm: Permutations II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // When the state length equals the number of elements, record the solution if state.count == choices.count { res.append(state) return } // Traverse all choices var duplicated: Set = [] for (i, choice) in choices.enumerated() { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i], !duplicated.contains(choice) { // Attempt: make choice, update state duplicated.insert(choice) // Record the selected element value selected[i] = true state.append(choice) // Proceed to the next round of selection backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeLast() } } } /* Permutations II */ func permutationsII(nums: [Int]) -> [[Int]] { var state: [Int] = [] var selected = Array(repeating: false, count: nums.count) var res: [[Int]] = [] backtrack(state: &state, choices: nums, selected: &selected, res: &res) return res } ``` === "JS" ```javascript title="permutations_ii.js" /* Backtracking algorithm: Permutations II */ function backtrack(state, choices, selected, res) { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices const duplicated = new Set(); choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.has(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "TS" ```typescript title="permutations_ii.ts" /* Backtracking algorithm: Permutations II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // When the state length equals the number of elements, record the solution if (state.length === choices.length) { res.push([...state]); return; } // Traverse all choices const duplicated = new Set(); choices.forEach((choice, i) => { // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.has(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } }); } /* Permutations II */ function permutationsII(nums: number[]): number[][] { const res: number[][] = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "Dart" ```dart title="permutations_ii.dart" /* Backtracking algorithm: Permutations II */ void backtrack( List state, List choices, List selected, List> res, ) { // When the state length equals the number of elements, record the solution if (state.length == choices.length) { res.add(List.from(state)); return; } // Traverse all choices Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice); // Record the selected element value selected[i] = true; state.add(choice); // Proceed to the next round of selection backtrack(state, choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.removeLast(); } } } /* Permutations II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } ``` === "Rust" ```rust title="permutations_ii.rs" /* Backtracking algorithm: Permutations II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // When the state length equals the number of elements, record the solution if state.len() == choices.len() { res.push(state); return; } // Traverse all choices let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i] && !duplicated.contains(&choice) { // Attempt: make choice, update state duplicated.insert(choice); // Record the selected element value selected[i] = true; state.push(choice); // Proceed to the next round of selection backtrack(state.clone(), choices, selected, res); // Backtrack: undo choice, restore to previous state selected[i] = false; state.pop(); } } } /* Permutations II */ fn permutations_ii(nums: &mut [i32]) -> Vec> { let mut res = Vec::new(); backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); res } ``` === "C" ```c title="permutations_ii.c" /* Backtracking algorithm: Permutations II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // When the state length equals the number of elements, record the solution if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Traverse all choices bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated[choice]) { // Attempt: make choice, update state duplicated[choice] = true; // Record the selected element value selected[i] = true; state[stateSize] = choice; // Proceed to the next round of selection backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Backtrack: undo choice, restore to previous state selected[i] = false; } } } /* Permutations II */ int **permutationsII(int *nums, int numsSize, int *returnSize) { int *state = (int *)malloc(numsSize * sizeof(int)); bool *selected = (bool *)malloc(numsSize * sizeof(bool)); for (int i = 0; i < numsSize; i++) { selected[i] = false; } int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); *returnSize = 0; backtrack(state, 0, nums, numsSize, selected, res, returnSize); free(state); free(selected); return res; } ``` === "Kotlin" ```kotlin title="permutations_ii.kt" /* Backtracking algorithm: Permutations II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // When the state length equals the number of elements, record the solution if (state.size == choices.size) { res.add(state.toMutableList()) return } // Traverse all choices val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if (!selected[i] && !duplicated.contains(choice)) { // Attempt: make choice, update state duplicated.add(choice) // Record the selected element value selected[i] = true state.add(choice) // Proceed to the next round of selection backtrack(state, choices, selected, res) // Backtrack: undo choice, restore to previous state selected[i] = false state.removeAt(state.size - 1) } } } /* Permutations II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } ``` === "Ruby" ```ruby title="permutations_ii.rb" ### Backtracking: permutations II ### def backtrack(state, choices, selected, res) # When the state length equals the number of elements, record the solution if state.length == choices.length res << state.dup return end # Traverse all choices duplicated = Set.new choices.each_with_index do |choice, i| # Pruning: do not allow repeated selection of elements and do not allow repeated selection of equal elements if !selected[i] && !duplicated.include?(choice) # Attempt: make choice, update state duplicated.add(choice) selected[i] = true state << choice # Proceed to the next round of selection backtrack(state, choices, selected, res) # Backtrack: undo choice, restore to previous state selected[i] = false state.pop end end end ### Permutations II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ``` Assuming elements are pairwise distinct, there are $n!$ (factorial) permutations of $n$ elements. When recording results, we need to copy a list of length $n$, using $O(n)$ time. **Therefore, the time complexity is $O(n! \cdot n)$**. The maximum recursion depth is $n$, using $O(n)$ stack frame space. `selected` uses $O(n)$ space. At most $n$ `duplicated` sets exist simultaneously, using $O(n^2)$ space. **Therefore, the space complexity is $O(n^2)$**. ### 3.   Comparison of Two Pruning Methods Note that although both `selected` and `duplicated` are used for pruning, they have different objectives. - **Pruning duplicate choices**: There is only one `selected` throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in `state`. - **Pruning equal elements**: Each round of choices (each `backtrack` function call) contains a `duplicated` set. It records which elements have been chosen in this round's iteration (the `for` loop), and its purpose is to ensure that equal elements are chosen only once. Figure 13-9 shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation. ![Effective scope of two pruning conditions](permutations_problem.assets/permutations_ii_pruning_summary.png){ class="animation-figure" }

Figure 13-9   Effective scope of two pruning conditions