Вот минимальное решение с использованием библиотеки itertools
:
from itertools import permutations, chain
solve = lambda n: [(1,)*n] + list(set(chain(*[permutations((2,)*i + (1,)*(n-2*i)) for i in range(1, n//2+1)])))
Для вашего примера введите:
> solve(3)
[(1, 1, 1), (1, 2), (2, 1)]
Как это работает?
Проще увидеть, что происходит, если мы сделаем шаг назад:
def solve(n):
combinations = [(1,)*n]
for i in range(1, n//2+1):
combinations.extend(permutations((2,)*i + (1,)*(n-2*i)))
return list(set(combinations))
Самый тривиальный случай - это случай, когда вы делаете один шаг за раз, поэтому n шаги: (1,)*n
. Затем мы можем посмотреть, сколько двойных шагов мы можем сделать максимум, и это пол n , деленный на 2: n//2
. Затем мы повторяем возможные двойные шаги: попробуйте добавить двойной шаг к каждой итерации (2,)*i
, заполняя оставшееся пространство отдельными шагами (1,)*(n-2*i)
.
Функция перестановок из itertools сгенерирует все возможные перестановки одиночных и двойных шагов для этой итерации. При вводе (1,1,2) он будет генерировать (1,1,2), (1,2,1) и (2,1,1). В конце мы используем способ преобразования результата в set
, чтобы удалить дубликаты, а затем преобразовать его обратно в список.
Обобщение для любого количества и длины шагов (не оптимально!)
Один вкладыш:
from itertools import permutations, chain, combinations_with_replacement
solve = lambda n, steps: list(set(chain(*[permutations(sequence) for sequence in chain(*[combinations_with_replacement(steps, r) for r in range(n//min(steps)+1)]) if sum(sequence) == n])))
Пример вывода:
> solve(8, [2,3])
[(3, 2, 3), (2, 3, 3), (2, 2, 2, 2), (3, 3, 2)]
Версия для чтения легче:
def solve(n, steps):
result = []
for sequence_length in range(n//min(steps)+1):
sequences = combinations_with_replacement(steps, sequence_length)
for sequence in sequences:
if sum(sequence) == n:
result.extend(permutations(sequence))
return list(set(result))