Files
hello-algo/ru/docs/chapter_backtracking/subset_sum_problem.md
2026-01-23 00:59:30 +08:00

13 KiB
Raw Blame History

Задача о сумме подмножеств

Случай без повторяющихся элементов

!!! question

Дан массив положительных целых чисел `nums` и целевое положительное целое число `target`. Найдите все возможные комбинации, сумма элементов которых равна `target`. В заданном массиве нет повторяющихся элементов, каждый элемент может быть выбран несколько раз. Верните эти комбинации в виде списка, в котором не должно быть повторяющихся комбинаций.

Например, для входного множества \{3, 4, 5\} и целевого числа 9 решениями будут \{3, 3, 3\}, \{4, 5\}. Следует обратить внимание на следующие два момента:

  • элементы входного множества можно выбирать неограниченное количество раз;
  • порядок элементов в подмножестве не имеет значения, например \{4, 5\} и \{5, 4\} — одно и то же подмножество.

Сравнение с решением задачи о полных перестановках

Подобно задаче о полных перестановках, процесс генерации подмножеств можно представить как серию выборов, а в процессе выбора в реальном времени обновлять сумму элементов. Когда сумма элементов равна target, подмножество записывается в список результатов.

Однако, в отличие от задачи о полных перестановках, в данной задаче элементы множества можно выбирать неограниченное количество раз, поэтому нет необходимости использовать булев список selected для записи выбранных элементов. Для получения начального решения можно просто немного изменить код для полных перестановок.

[file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive}

При вводе в этот код массива [3, 4, 5] и целевого элемента 9 будет выведено [3, 3, 3], [4, 5], [5, 4]. Хотя удалось найти все подмножества с суммой 9, среди них есть повторяющиеся подмножества [4, 5] и $[5, 4]$.

Это происходит потому, что процесс поиска различает порядок выбора, тогда как в подмножествах порядок элементов не важен. Как показано на рисунке ниже, сначала выбрать 4, а затем 5 и сначала выбрать 5, а затем 4 — это разные ветви, но они соответствуют одному и тому же подмножеству.

Поиск подмножеств и обрезка по превышению целевого значения

Одним из очевидных подходов к устранению повторяющихся подмножеств является удаление дубликатов из списка результатов. Однако этот метод очень неэффективен по двум причинам:

  • Когда в массиве много элементов, особенно когда значение target велико, процесс поиска генерирует множество повторяющихся подмножеств.
  • Сравнение подмножеств (массивов) на различия — очень затратная по времени операция. Она требует сначала сортировки массивов, затем сравнения различий каждого элемента в массиве.

Обрезка повторяющихся подмножеств

Рассмотрим устранение дубликатов в процессе поиска с помощью обрезки. На рисунке ниже показано, что повторяющиеся подмножества возникают при выборе элементов массива в разном порядке, например в следующих случаях:

  1. Пусть на первом и втором этапах выбираются 3 и 4 соответственно, создаются все подмножества, содержащие эти два элемента, обозначенные как [3, 4, \dots].
  2. Затем если на первом этапе выбирается 4, то на втором этапе следует пропустить $3$, так как подмножество [4, 3, \dots] полностью повторяет подмножество, созданное на этапе 1..

В процессе поиска выбор на каждом уровне осуществляется слева направо. Поэтому чем правее ветвь, тем больше она обрезается.

  1. На первых двух этапах выбираются 3 и 5 и создаются подмножества [3, 5, \dots].
  2. На первых двух этапах выбираются 4 и 5 и создаются подмножества [4, 5, \dots].
  3. Если на первом этапе выбирается 5, то на втором этапе следует пропустить 3 и $4$, так как подмножества [5, 3, \dots] и [5, 4, \dots] полностью повторяют подмножества, описанные на этапах 1. и 2..

Повторяющиеся подмножества, полученные в результате различного порядка выбора

Обобщим эту мысль. Пусть задан входной массив [x_1, x_2, \dots, x_n]. Тогда в процессе поиска последовательность выбора [x_{i_1}, x_{i_2}, \dots, x_{i_m}] должна удовлетворять условию i_1 \leq i_2 \leq \dots \leq i_m, в противном случае она приведет к дубликатам, и ее нужно обрезать.

Код реализации

Для реализации этой обрезки мы инициализируем переменную start, которая указывает начальную точку обхода. После выбора x_{i} следующая итерация начинается с индекса $i$. Это позволяет для последовательности выбора соблюдать условие i_1 \leq i_2 \leq \dots \leq i_m, обеспечивая уникальность подмножеств.

Кроме того, в код были внесены следующие две оптимизации:

  • Перед началом поиска массив nums сортируется. При обходе всех вариантов, если сумма подмножества превышает target, цикл завершается, так как последующие элементы больше, и их сумма также превысит target.
  • Исключение переменной total, подсчет суммы элементов осуществляется с помощью вычитания из target. Решение фиксируется, когда target равен 0.
[file]{subset_sum_i}-[class]{}-[func]{subset_sum_i}

На рисунке ниже показан полный процесс поиска с возвратом для массива [3, 4, 5] и целевого элемента 9.

Процесс поиска с возвратом для реализации задачи о сумме подмножеств I

Случай с повторяющимися элементами

!!! question

Дан массив положительных целых чисел `nums` и целевое положительное целое число `target`. Найдите все возможные комбинации, сумма элементов которых равна `target`. **Заданный массив может содержать повторяющиеся элементы, каждый элемент может быть выбран только один раз**. Верните эти комбинации в виде списка, в котором не должно быть повторяющихся комбинаций.

В отличие от предыдущей задачи входной массив может содержать повторяющиеся элементы, что создает новую проблему. Например, для массива [4, \hat{4}, 5] и целевого элемента 9 текущий код выдает результат [4, 5], [\hat{4}, 5], что приводит к повторяющимся подмножествам.

Причина этих повторов в том, что равные элементы выбираются несколько раз на одном этапе. На рисунке ниже показано, что на первом этапе есть три варианта выбора, два из которых равны 4. Это приводит к двум повторяющимся ветвям поиска и, следовательно, к повторяющимся подмножествам. Аналогично два элемента 4 на втором этапе также создают повторяющиеся подмножества.

Повторяющиеся подмножества из-за равных элементов

Обрезка равных элементов

Для решения этой проблемы необходимо сделать выбор равных элементов на каждом этапе однократным. Реализация этого подхода довольно изящна: поскольку массив отсортирован, равные элементы находятся рядом друг с другом. Это означает, что если текущий элемент равен предыдущему, то он уже был выбран, и его следует пропустить.

В то же время в этой задаче предусмотрено, что каждый элемент массива может быть выбран только один раз. К счастью, можно использовать переменную start для выполнения этого ограничения: после выбора x_{i} начинаем следующий цикл с индекса i + 1. Это позволяет исключить повторяющиеся подмножества и избежать повторного выбора элементов.

Код реализации

[file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii}

На рисунке ниже демонстрируется процесс обратного отслеживания для массива [4, 4, 5] и целевого элемента 9, включающий четыре вида обрезки. Проанализируйте рисунок и комментарии в коде, чтобы лучше понять весь процесс поиска и как работают различные операции обрезки.

Процесс поиска с возвратом для реализации задачи о сумме подмножеств II