Вы можете рекурсивно подойти к проблеме с точки зрения основания лестницы. Стратегия состоит в том, чтобы суммировать количество шаблонов уровней следующего шага для каждого базового размера.
Например, при 6 кирпичах первый вызов прошел бы через базовые размеры n = 5,4,3,2 и сделал бы рекурсивные вызовы, чтобы узнать, сколько комбинаций возможно для уровней следующего шага, используя оставшиеся кирпичи и с максимальная база n-1. Сумма отсчетов следующих уровней будет составлять общее количество возможных схем лестниц.
В верхней части лестницы вам нужно как минимум 3 кубика, чтобы добавить более одного уровня, чтобы вы могли остановить рекурсию со счетом 1, когда осталось менее 3 кубиков. Это свернет рекурсивные вызовы для формирования больших итогов и даст правильный ответ после завершения исходного вызова.
Чтобы оптимизировать этот процесс, вы можете использовать запоминание, а также замыкать вычисления, используя базовый размер, указанный в каждой рекурсии.
Для данной базы наибольшее количество кирпичей, которые можно использовать, будет суммой чисел от 1 до этой базы. Это можно рассчитать по формуле Гаусса: base*(base+1)/2
. Если у вас больше кирпичей, чем максимальное количество кирпичей для базы, то вы можете остановить рекурсию и вернуть нулевой счет (так как у вас слишком много оставшихся кирпичей и вы не сможете разместить их все по базе предыдущего уровня)
Еще один способ оптимизировать вычисления - это циклически проходить через базовые размеры в порядке убывания Это позволит вам остановить цикл, как только вы получите счетчик нуля для следующих уровней, что означает, что для этого базового размера (или любого меньшего базового размера) осталось слишком много кубиков
Вот пример (использование lru_cache для запоминания):
from functools import lru_cache
@lru_cache(None)
def stairCount(N,base=None):
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
count = 0
while True:
nextLevels = stairCount(N-base,base-1)
if nextLevels == 0: break
count += nextLevels
base = base - 1
return count
С этими оптимизациями функция будет реагировать менее чем за секунду до 600 блоков (в зависимости от скорости вашего компьютера).
При использовании списка Python вы могли бы написать эту функцию более кратко (хотя она потеряла бы убывающую оптимизацию базового порядка ≈ 10%):
@lru_cache(None)
def stairCount(N,base=None):
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
return sum(stairCount(N-b,b-1) for b in range(2,base+1))
РЕДАКТИРОВАТЬ Вот версия с «ручным» запоминанием (т.е. без использования functools):
def stairCount(N,base=None,memo=dict()):
memoKey = (N,base)
if memoKey in memo: return memo[memoKey]
base = min(base or N-1,N)
if N > base*(base+1)//2: return 0
if N < 3: return 1
count = 0
while True:
nextLevels = stairCount(N-base,base-1,memo)
if nextLevels == 0: break
count += nextLevels
base = base - 1
memo[memoKey] = count
return count