Backtracking — это DFS с отменой. Разбираем шаблон CHOOSE-EXPLORE-UNCHOOSE на примерах подмножеств, перестановок и N-queens. Научитесь решать за 3 шага.
Вы решаете задачи на LeetCode, уверенно работаете с графами и динамическим программированием, но как только встречаете «все подмножества» или «все возможные расстановки» — впадаете в ступор? Знакомо. Backtracking кажется магией, пока не увидишь его истинную структуру.
Секрет в том, что backtracking — это просто DFS с кнопкой «отмена». Не нужно запоминать десятки решений. Достаточно понять один шаблон.
Представьте, что каждая задача на backtracking — это дерево. Корень — пустое частичное решение. Каждое ребро — выбор, который вы делаете. Лист — готовое решение. Вы обходите это дерево в глубину: строите решение, спускаясь, и разбираете его, поднимаясь обратно.
Весь алгоритм укладывается в три строки:
path.append(choice) # ВЫБОР
backtrack(next) # ИССЛЕДОВАНИЕ
path.pop() # ОТМЕНАВыбрать, исследовать, отменить. Подмножества, перестановки, комбинации, N-queens, судоку, поиск слова — все это один и тот же цикл. Разница лишь в том, что считается выбором, когда остановиться и можно ли отсечь ветку раньше.
Строка path.pop() кажется необязательной. Код работает, выводит что-то похожее на правду, но неверно. Вы отметили клетку занятой и забыли снять отметку. Добавили число и не убрали. В результате половина «перестановок» содержит неверные элементы, а вы ищете опечатку, которой нет.
Ошибка не в синтаксисе — она в структуре, которая не видна в коде.
results.append(path) # НЕВЕРНО: сохраняется ссылка, path мутирует
results.append(list(path)) # ВЕРНО: копия на момент записиПервый вариант даёт массив правильной длины, где каждый элемент — пустой список. Потому что path продолжал меняться после сохранения. На собеседовании на эту ловушку тратят 20 минут.
Обе ошибки невозможно заметить перечитыванием — код выглядит корректным. Проблема живёт во времени, по мере роста и сжатия состояния при рекурсии.
Чтобы понять, нужно увидеть, как путь растёт и сжимается шаг за шагом. ВЫБОР 1: path = [1]. ВЫБОР 2: path = [1,2]. Записали. ОТМЕНА: path = [1] снова. Когда видишь, как массив физически уменьшается, становится очевидно: отмена — не опция, а единственный способ сохранить честность каждой ветки.
Один раз увидев выполнение, вы перестаёте заучивать решения. Вы видите дерево, пишете три строки и адаптируете базовый случай и отсечения под задачу. N-queens перестаёт пугать, потому что это тот же скелет, что и у подмножеств, с одной дополнительной проверкой — не атакована ли клетка. Эта проверка и есть тот самый «back» в backtracking, отсекающий экспоненциальное поддерево одной строкой.
Перестаньте коллекционировать решения backtracking. Возьмите 3-4 уже написанных и для каждого ответьте: какой здесь выбор, какой базовый случай, что отсекается. Когда сможете заполнить эти три пункта для любой новой задачи — вы освоили паттерн, а не факты.
Особенно полезно увидеть рекурсию в действии первые несколько раз — с пошаговым отображением пути и результатов. Напишите простой визуализатор или используйте готовые инструменты. Наблюдение за выбором и отменой — самый быстрый способ перестать бояться техники, которая на самом деле оказывается простейшей.
Backtracking — не категория сложных задач. Это один цикл, который вы отменяете. Увидьте дерево — и заклинания превратятся в синтаксис.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →