377. Combination Sum IV
Содержание
Задача
Дан массив различных целых чисел nums
и целевое целое число target
от 1 до 1000. Нужно вернуть количество возможных комбинаций, которые в сумме дают target
.
Подсказки
- Построить дерево решений
- Задачу можно решить путем разложения ее на меньшие подзадачи с помощью динамического программирования.
Нахождение целевого значения в дереве решений
Подход
Если целевое значение - target=7
и nums=[2, 3, 4]
, то в дереве решений может быть несколько путей до этого числа. Например: 2-2-3
, 2-3-2
, 3-2-2
, 3-4
, 4-3
, 5-2
.
Во время подсчета различных путей мы получаем различные суммы, например: 2-2-3
, сначала сумма 2, потом 4, потом 7.
Следующи возможный путь начинается с 3. Чтобы проверить, подходит данный путь или нет, мы можем рассчитать недостающее число до target
: 7-3=4. Мы получили 4, но ранее мы уже получали такую сумму и знаем, что если на данном этапе мы хотим знать будет ли какое количество вариантов для суммы 4, то в итоге мы найдем решение.
Это - особенность динамического программирования, когда на каждом этапе мы используем уже подсчитанные данные, пройденные до текущего момента.
Мы будем использовать массив sums
, где sums[s]
будет хранить количество комбинаций, которые дают сумму s
.
Для каждого числа s
от 1 до target
, мы будем итерировать по каждому числу в nums
и прибавлять sums[s-num]
к sums[s]
.
Пример:
sums[4] = sums[4-2] + sums[4-4] + sums[4-4]
Почему мы рассматриваем числа от 1 до target?
Целью является поиск всех возможных комбинаций чисел из nums
, которые в сумме дают target
. Начиная с 1 и заканчивая target
, мы стремимся найти все возможные комбинации для каждого промежуточного значения. Таким образом, когда мы достигаем target
, у нас уже будут вычислены комбинации для всех предыдущих значений, что позволит быстро найти ответ для target
.
Зачем нам нужен индекс с нулевым значением в массиве?
Значение sums[0] = 1
может показаться не совсем интуитивным, но оно имеет особый смысл. Это значение говорит нам о том, что есть один способ получить сумму 0 — не использовать ни одного числа из nums
. Это начальное условие необходимо для корректной работы алгоритма, так как при добавлении каждого нового числа из nums
к уже найденным комбинациям мы будем обращаться к этому значению.
Рассмотрим пример. Пусть nums = [1,2,3]
и target = 4
. Когда мы рассматриваем число 1 (первый шаг итерации), наш алгоритм будет искать число комбинаций, которые дают сумму 1 - 1 = 0. И здесь значение sums[0]
приходит на помощь: благодаря ему алгоритм “понимает”, что существует одна такая комбинация.
Алгоритм
- Создаем список
sums
с длинойtarget + 1
и заполняем его нулями. Этот список будет представлять количество комбинаций, которые приводят к определенной (промежуточной) сумме. - Устанавливаем
sums[0]
в 1, так как есть только один способ получить сумму 0: при пустомnums
. - Основной цикл (построение таблицы):
- Перебираем все возможные суммы от 1 до
target
(включительно). Допустим, текущее число обозначено какs
- Теперь перебираем каждое число
n
изnums
.- Находим остаточную сумму
s - n
- Прибавляем к
sums[s]
значениеsums[s-n]
, так как любая комбинация, ведущая кs-n
, может быть дополнена числомn
, чтобы достичьs
.
- Находим остаточную сумму
- Теперь перебираем каждое число
- Перебираем все возможные суммы от 1 до
Решение
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
sums = [0] * (target + 1)
sums[0] = 1 # entrypoint of dynamic p: sum 0 can be only in case if nums is empty
for s in range(1, target + 1):
for n in nums: # check all paths
remainder = s - n
if remainder >= 0: # use only positive indexes (sums)
sums[s] += sums[remainder] # count to previous results
return sums[target]
Данное решение можно отобразить с использованием словаря для всех сумм:
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
sums = {0:1}
for s in range(1, target + 1):
sums[s] = 0
for n in nums:
remainder = s - n
sums[s] += sums.get(remainder, 0)
return sums[target]