Признак в условии
- «все подмножества / перестановки / комбинации»
- «расставить так, чтобы не конфликтовали»
- ответ - не число, а список вариантов
Каркас
const walk = (позиция, текущее) => {
if (готово) { out.push([...текущее]); return; } // КОПИЯ, не ссылка
for (const вариант of варианты(позиция)) {
if (!допустим(вариант)) continue; // отсечение
текущее.push(вариант);
walk(позиция + 1, текущее);
текущее.pop(); // откат
}
};
Два ключевых решения
Копия при сохранении: out.push([...current]) - иначе в результат попадут ссылки
на один массив, который в конце опустеет.
Параметр start отсекает перестановки одного набора: walk(i, …) разрешает повтор
элемента, walk(i + 1, …) запрещает.
Сложность
Экспоненциальная: подмножества O(2ⁿ·n), перестановки O(n!·n). Отсечения решают, будет ли это работать на практике.
Частые ошибки
- сохранение без копии
- откатывается массив, но не флаг «использовано»
- нет отсечения, и дерево перебора взрывается
- без start в результате появляются перестановки одного набора
Мнемоника
«Выбрал, углубился, откатил»
Не путать с
- Обход в глубину - возврат нужен там, где после ветки состояние надо откатить: убрать элемент из текущего набора, снять пометку
- Динамическое программирование - если подзадачи повторяются, перебор превращается в динамику запоминанием; если нет - остаётся перебором
Задачи на этот паттерн
- все подмножества
- все перестановки
- наборы с заданной суммой
- правильные скобочные последовательности
- буквы номера телефона
- поиск слова в сетке