--- comments: true --- # 13.2   Задача о перестановках Задача о перестановках является типичным применением алгоритма поиска с возвратом. Ее определение состоит в том, чтобы для данного множества элементов (например, массива или строки) найти все возможные перестановки этих элементов. В таблице 13-2 приведено несколько примеров входных массивов и соответствующих им перестановок.

Таблица 13-2   Примеры перестановок

| Входной массив | Все перестановки | | :------------- | :----------------------------------------------------------------- | | $[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   Случай без равных элементов !!! question Дан массив целых чисел, в котором нет повторяющихся элементов. Верните все возможные перестановки. С точки зрения поиска с возвратом **процесс построения перестановок можно представить как результат последовательности выборов**. Пусть входной массив равен $[1, 2, 3]$. Если мы сначала выберем $1$ , затем $3$ , а потом $2$ , то получим перестановку $[1, 3, 2]$ . Откат здесь означает отмену одного из выборов с последующей попыткой других вариантов. С точки зрения кода поиска с возвратом множество кандидатов `choices` состоит из всех элементов входного массива, а состояние `state` - из элементов, уже выбранных к текущему моменту. Поскольку каждый элемент разрешено выбирать только один раз, **все элементы в `state` должны быть уникальны**. Как показано на рисунке 13-5, процесс поиска можно развернуть в дерево рекурсии, где каждый узел представляет текущее состояние `state` . Начиная от корня, после трех раундов выбора мы попадаем в листья, и каждый лист соответствует одной перестановке. ![Дерево рекурсии для перестановок](permutations_problem.assets/permutations_i.png){ class="animation-figure" }

Рисунок 13-5   Дерево рекурсии для перестановок

### 1.   Обрезка повторного выбора Чтобы гарантировать, что каждый элемент выбирается только один раз, введем булев массив `selected` , где `selected[i]` обозначает, был ли уже выбран `choices[i]` , и на его основе выполним следующую обрезку. - После того как сделан выбор `choice[i]` , мы присваиваем `selected[i]` значение $\text{True}$ , тем самым отмечая, что этот элемент уже выбран. - При обходе списка вариантов `choices` пропускаем все уже выбранные элементы, то есть выполняем обрезку. Как показано на рисунке 13-6, если в первом раунде мы выберем 1 , во втором - 3 , а в третьем - 2 , то во втором раунде нужно отсечь ветвь элемента 1 , а в третьем - ветви элементов 1 и 3 . ![Пример обрезки в задаче о перестановках](permutations_problem.assets/permutations_i_pruning.png){ class="animation-figure" }

Рисунок 13-6   Пример обрезки в задаче о перестановках

Как видно на рисунке 13-6, такая обрезка уменьшает размер пространства поиска с $O(n^n)$ до $O(n!)$ . ### 2.   Реализация кода После прояснения всей логики можно просто «заполнить пропуски» в шаблоне поиска с возвратом. Чтобы сократить общий объем кода, мы не будем отдельно реализовывать каждую функцию из каркаса, а раскроем их прямо внутри `backtrack()` : === "Python" ```python title="permutations_i.py" def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): """Алгоритм бэктрекинга: все перестановки I""" # Когда длина состояния равна числу элементов, записать решение if len(state) == len(choices): res.append(list(state)) return # Перебор всех вариантов выбора for i, choice in enumerate(choices): # Отсечение: нельзя выбирать один и тот же элемент повторно if not selected[i]: # Попытка: сделать выбор и обновить состояние selected[i] = True state.append(choice) # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = False state.pop() def permutations_i(nums: list[int]) -> list[list[int]]: """Все перестановки I""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res ``` === "C++" ```cpp title="permutations_i.cpp" /* Алгоритм бэктрекинга: все перестановки I */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // Когда длина состояния равна числу элементов, записать решение if (state.size() == choices.size()) { res.push_back(state); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push_back(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop_back(); } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ void backtrack(List state, int[] choices, boolean[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.remove(state.size() - 1); } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.Add(choice); // Перейти к следующему выбору Backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Все перестановки I */ List> PermutationsI(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } ``` === "Go" ```go title="permutations_i.go" /* Алгоритм бэктрекинга: все перестановки I */ func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // Когда длина состояния равна числу элементов, записать решение if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Перебор всех вариантов выбора for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Отсечение: нельзя выбирать один и тот же элемент повторно if !(*selected)[i] { // Попытка: сделать выбор и обновить состояние (*selected)[i] = true *state = append(*state, choice) // Перейти к следующему выбору backtrackI(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // Когда длина состояния равна числу элементов, записать решение if state.count == choices.count { res.append(state) return } // Перебор всех вариантов выбора for (i, choice) in choices.enumerated() { // Отсечение: нельзя выбирать один и тот же элемент повторно if !selected[i] { // Попытка: сделать выбор и обновить состояние selected[i] = true state.append(choice) // Перейти к следующему выбору backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeLast() } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ function backtrack(state, choices, selected, res) { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки I */ function permutationsI(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "TS" ```typescript title="permutations_i.ts" /* Алгоритм бэктрекинга: все перестановки I */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ void backtrack( List state, List choices, List selected, List> res, ) { // Когда длина состояния равна числу элементов, записать решение if (state.length == choices.length) { res.add(List.from(state)); return; } // Перебор всех вариантов выбора for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.removeLast(); } } } /* Все перестановки I */ List> permutationsI(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } ``` === "Rust" ```rust title="permutations_i.rs" /* Алгоритм бэктрекинга: все перестановки I */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // Когда длина состояния равна числу элементов, записать решение if state.len() == choices.len() { res.push(state); return; } // Перебор всех вариантов выбора for i in 0..choices.len() { let choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if !selected[i] { // Попытка: сделать выбор и обновить состояние selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state.clone(), choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } } } /* Все перестановки I */ fn permutations_i(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_i.c" /* Алгоритм бэктрекинга: все перестановки I */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // Когда длина состояния равна числу элементов, записать решение if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Перебор всех вариантов выбора for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true; state[stateSize] = choice; // Перейти к следующему выбору backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки I */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // Когда длина состояния равна числу элементов, записать решение if (state.size == choices.size) { res.add(state.toMutableList()) return } // Перебор всех вариантов выбора for (i in choices.indices) { val choice = choices[i] // Отсечение: нельзя выбирать один и тот же элемент повторно if (!selected[i]) { // Попытка: сделать выбор и обновить состояние selected[i] = true state.add(choice) // Перейти к следующему выбору backtrack(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeAt(state.size - 1) } } } /* Все перестановки I */ fun permutationsI(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } ``` === "Ruby" ```ruby title="permutations_i.rb" ### Алгоритм бэктрекинга: все перестановки I ### def backtrack(state, choices, selected, res) # Когда длина состояния равна числу элементов, записать решение if state.length == choices.length res << state.dup return end # Перебор всех вариантов выбора choices.each_with_index do |choice, i| # Отсечение: нельзя выбирать один и тот же элемент повторно unless selected[i] # Попытка: сделать выбор и обновить состояние selected[i] = true state << choice # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.pop end end end ### Все перестановки I ### def permutations_i(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ``` ??? pythontutor "Визуализация кода"
## 13.2.2   Учет равных элементов !!! question Дан массив целых чисел, **который может содержать повторяющиеся элементы**. Верните все неповторяющиеся перестановки. Пусть входной массив равен $[1, 1, 2]$ . Чтобы различать два одинаковых элемента $1$ , будем обозначать второй из них как $\hat{1}$ . Как показано на рисунке 13-7, описанный выше метод создаст результат, половина которого окажется дублирующейся. ![Повторяющиеся перестановки](permutations_problem.assets/permutations_ii.png){ class="animation-figure" }

Рисунок 13-7   Повторяющиеся перестановки

Как же убрать повторяющиеся перестановки? Самый прямолинейный способ - воспользоваться хеш-множеством и удалить дубликаты уже после генерации результата. Но это не слишком изящно, **потому что ветви поиска, порождающие дубликаты, вообще не нужно посещать: их следует распознавать заранее и отсекать**, что дополнительно повышает эффективность алгоритма. ### 1.   Обрезка равных элементов Как видно на рисунке 13-8, в первом раунде выбрать $1$ или выбрать $\hat{1}$ - это одно и то же, а значит, все перестановки, полученные из этих двух выборов, будут дублироваться. Поэтому ветвь $\hat{1}$ нужно отсечь. Точно так же, если в первом раунде выбрать $2$ , то во втором раунде выборы $1$ и $\hat{1}$ снова создадут дублирующиеся ветви, поэтому и в этом случае ветвь $\hat{1}$ нужно отсечь. Иначе говоря, **наша цель заключается в том, чтобы на каждом раунде выбора каждый из нескольких равных элементов выбирался только один раз**. ![Обрезка повторяющихся перестановок](permutations_problem.assets/permutations_ii_pruning.png){ class="animation-figure" }

Рисунок 13-8   Обрезка повторяющихся перестановок

### 2.   Реализация кода На основе решения из предыдущей задачи можно на каждом раунде выбора заводить хеш-множество `duplicated` , которое будет записывать элементы, уже встречавшиеся в этом раунде, и отсекать повторы: === "Python" ```python title="permutations_ii.py" def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): """Алгоритм бэктрекинга: все перестановки II""" # Когда длина состояния равна числу элементов, записать решение if len(state) == len(choices): res.append(list(state)) return # Перебор всех вариантов выбора duplicated = set[int]() for i, choice in enumerate(choices): # Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if not selected[i] and choice not in duplicated: # Попытка: сделать выбор и обновить состояние duplicated.add(choice) # Записать значения уже выбранных элементов selected[i] = True state.append(choice) # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = False state.pop() def permutations_ii(nums: list[int]) -> list[list[int]]: """Все перестановки II""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res ``` === "C++" ```cpp title="permutations_ii.cpp" /* Алгоритм бэктрекинга: все перестановки II */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { // Когда длина состояния равна числу элементов, записать решение if (state.size() == choices.size()) { res.push_back(state); return; } // Перебор всех вариантов выбора unordered_set duplicated; for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && duplicated.find(choice) == duplicated.end()) { // Попытка: сделать выбор и обновить состояние duplicated.emplace(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push_back(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop_back(); } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ void backtrack(List state, int[] choices, boolean[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.size() == choices.length) { res.add(new ArrayList(state)); return; } // Перебор всех вариантов выбора Set duplicated = new HashSet(); for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.remove(state.size() - 1); } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ void Backtrack(List state, int[] choices, bool[] selected, List> res) { // Когда длина состояния равна числу элементов, записать решение if (state.Count == choices.Length) { res.Add(new List(state)); return; } // Перебор всех вариантов выбора HashSet duplicated = []; for (int i = 0; i < choices.Length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.Contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.Add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.Add(choice); // Перейти к следующему выбору Backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.RemoveAt(state.Count - 1); } } } /* Все перестановки II */ List> PermutationsII(int[] nums) { List> res = []; Backtrack([], nums, new bool[nums.Length], res); return res; } ``` === "Go" ```go title="permutations_ii.go" /* Алгоритм бэктрекинга: все перестановки II */ func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { // Когда длина состояния равна числу элементов, записать решение if len(*state) == len(*choices) { newState := append([]int{}, *state...) *res = append(*res, newState) } // Перебор всех вариантов выбора duplicated := make(map[int]struct{}, 0) for i := 0; i < len(*choices); i++ { choice := (*choices)[i] // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if _, ok := duplicated[choice]; !ok && !(*selected)[i] { // Попробовать: сделать выбор, обновить состояние // Записать значение уже выбранного элемента duplicated[choice] = struct{}{} (*selected)[i] = true *state = append(*state, choice) // Перейти к следующему выбору backtrackII(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние (*selected)[i] = false *state = (*state)[:len(*state)-1] } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { // Когда длина состояния равна числу элементов, записать решение if state.count == choices.count { res.append(state) return } // Перебор всех вариантов выбора var duplicated: Set = [] for (i, choice) in choices.enumerated() { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i], !duplicated.contains(choice) { // Попытка: сделать выбор и обновить состояние duplicated.insert(choice) // Записать значения уже выбранных элементов selected[i] = true state.append(choice) // Перейти к следующему выбору backtrack(state: &state, choices: choices, selected: &selected, res: &res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeLast() } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ function backtrack(state, choices, selected, res) { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора const duplicated = new Set(); choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.has(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки II */ function permutationsII(nums) { const res = []; backtrack([], nums, Array(nums.length).fill(false), res); return res; } ``` === "TS" ```typescript title="permutations_ii.ts" /* Алгоритм бэктрекинга: все перестановки II */ function backtrack( state: number[], choices: number[], selected: boolean[], res: number[][] ): void { // Когда длина состояния равна числу элементов, записать решение if (state.length === choices.length) { res.push([...state]); return; } // Перебор всех вариантов выбора const duplicated = new Set(); choices.forEach((choice, i) => { // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.has(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } }); } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ void backtrack( List state, List choices, List selected, List> res, ) { // Когда длина состояния равна числу элементов, записать решение if (state.length == choices.length) { res.add(List.from(state)); return; } // Перебор всех вариантов выбора Set duplicated = {}; for (int i = 0; i < choices.length; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice); // Записать значения уже выбранных элементов selected[i] = true; state.add(choice); // Перейти к следующему выбору backtrack(state, choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.removeLast(); } } } /* Все перестановки II */ List> permutationsII(List nums) { List> res = []; backtrack([], nums, List.filled(nums.length, false), res); return res; } ``` === "Rust" ```rust title="permutations_ii.rs" /* Алгоритм бэктрекинга: все перестановки II */ fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { // Когда длина состояния равна числу элементов, записать решение if state.len() == choices.len() { res.push(state); return; } // Перебор всех вариантов выбора let mut duplicated = HashSet::::new(); for i in 0..choices.len() { let choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i] && !duplicated.contains(&choice) { // Попытка: сделать выбор и обновить состояние duplicated.insert(choice); // Записать значения уже выбранных элементов selected[i] = true; state.push(choice); // Перейти к следующему выбору backtrack(state.clone(), choices, selected, res); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; state.pop(); } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { // Когда длина состояния равна числу элементов, записать решение if (stateSize == choicesSize) { res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); for (int i = 0; i < choicesSize; i++) { res[*resSize][i] = state[i]; } (*resSize)++; return; } // Перебор всех вариантов выбора bool duplicated[MAX_SIZE] = {false}; for (int i = 0; i < choicesSize; i++) { int choice = choices[i]; // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated[choice]) { // Попытка: сделать выбор и обновить состояние duplicated[choice] = true; // Записать значения уже выбранных элементов selected[i] = true; state[stateSize] = choice; // Перейти к следующему выбору backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false; } } } /* Все перестановки 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" /* Алгоритм бэктрекинга: все перестановки II */ fun backtrack( state: MutableList, choices: IntArray, selected: BooleanArray, res: MutableList?> ) { // Когда длина состояния равна числу элементов, записать решение if (state.size == choices.size) { res.add(state.toMutableList()) return } // Перебор всех вариантов выбора val duplicated = HashSet() for (i in choices.indices) { val choice = choices[i] // Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if (!selected[i] && !duplicated.contains(choice)) { // Попытка: сделать выбор и обновить состояние duplicated.add(choice) // Записать значения уже выбранных элементов selected[i] = true state.add(choice) // Перейти к следующему выбору backtrack(state, choices, selected, res) // Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.removeAt(state.size - 1) } } } /* Все перестановки II */ fun permutationsII(nums: IntArray): MutableList?> { val res = mutableListOf?>() backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) return res } ``` === "Ruby" ```ruby title="permutations_ii.rb" ### Алгоритм бэктрекинга: все перестановки II ### def backtrack(state, choices, selected, res) # Когда длина состояния равна числу элементов, записать решение if state.length == choices.length res << state.dup return end # Перебор всех вариантов выбора duplicated = Set.new choices.each_with_index do |choice, i| # Отсечение: нельзя выбирать один и тот же элемент повторно и нельзя повторно выбирать равные элементы if !selected[i] && !duplicated.include?(choice) # Попытка: сделать выбор и обновить состояние duplicated.add(choice) selected[i] = true state << choice # Перейти к следующему выбору backtrack(state, choices, selected, res) # Откат: отменить выбор и восстановить предыдущее состояние selected[i] = false state.pop end end end ### Все перестановки II ### def permutations_ii(nums) res = [] backtrack([], nums, Array.new(nums.length, false), res) res end ``` ??? pythontutor "Визуализация кода"
Если предположить, что все элементы попарно различны, то из $n$ элементов можно получить $n!$ перестановок. При записи результата требуется копировать список длины $n$ , что занимает $O(n)$ времени. **Следовательно, временная сложность равна $O(n!n)$** . Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ стековой памяти. Массив `selected` занимает $O(n)$ пространства. Одновременно может существовать до $n$ хеш-множеств `duplicated` , что дает $O(n^2)$ памяти. **Следовательно, пространственная сложность равна $O(n^2)$** . ### 3.   Сравнение двух видов обрезки Обратите внимание: хотя и `selected` , и `duplicated` используются для обрезки, их цели различаются. - **Обрезка повторного выбора**: во всем процессе поиска существует только один `selected` . Он записывает, какие элементы уже входят в текущее состояние, и нужен для того, чтобы один и тот же элемент не появлялся в `state` дважды. - **Обрезка равных элементов**: каждый раунд выбора (каждый вызов `backtrack`) содержит собственный `duplicated` . Он записывает, какие элементы уже выбирались в текущем раунде (`for` цикле), и нужен для того, чтобы равные элементы выбирались только один раз. На рисунке 13-9 показана область действия двух условий обрезки. Помните, что каждый узел дерева соответствует одному выбору, а путь от корня до листа образует одну перестановку. ![Область действия двух условий обрезки](permutations_problem.assets/permutations_ii_pruning_summary.png){ class="animation-figure" }

Рисунок 13-9   Область действия двух условий обрезки