Зачем в этой задаче вообще динамическое программирование?
И - нет, это не оно. Массив и цикл - это не определяющие признаки динамического программирования. Восходящим способом должна вычисляться некая функция F(n), результаты которой используются как есть на следующих итерациях: F(n + 1), F(n + 2), ... При этом, "мемоизация", как написал бот, значения функции нужна только до тех пор, пока есть потребность в этом значении.
Вот такую реализацию, например, можно назвать динамическим программированием:
combs = [[]]
for _ in range(4):
combs = [c + [b] for c in combs for b in (0, 1)]
print('\n'.join(' '.join(str(b) for b in c) for c in combs))
Здесь на каждом шаге формируем набор комбинаций, а на следующем - "удваиваем" его, добавляя 0 и 1 к каждой комбинации, соответственно. Каждый шаг использует данные предыдущего шага.
Или ещё можно сделать так:
combs = [[0], [1]]
for _ in range(2):
combs = [c + d for c in combs for d in combs]
print('\n'.join(' '.join(str(b) for b in c) for c in combs))
Здесь тоже используются результаты предыдущего шага, причём, дважды.
Если первая реализация строит 2 + 16 + 1 + 8 + 1 + 4 + 1 + 2 + 1 = 36 списков, увеличивая строку на 1 элемент в каждой итерации, то вторая строит 3 + 4 + 1 + 16 + 1 = 25 списков, удваивая строку в каждой итерации. И то, и то - экспонента от длины строки, но вторая экспонента растёт медленнее. timeit показывает разницу почти в три раза в пользу второго алгоритма.
А если немного подхимичить и сделать комбинацию первого и второго алгоритмов, то можно использовать оптимизацию и для длин, не являющихся степенями двойки. Принцип - такой же, как и в возведении в натуральные степени с делением показателя на 2 в каждой итерации.
1 [[0], [1]]
2 [[0, 0], [0, 1], [1, 0], [1, 1]]
3 [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]
4 [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 0, 1, 1], [0, 1, 0, 0], [0, 1, 0, 1], [0, 1, 1, 0], [0, 1, 1, 1], [1, 0, 0, 0], [1, 0, 0, 1], [1, 0, 1, 0], [1, 0, 1, 1], [1, 1, 0, 0], [1, 1, 0, 1], [1, 1, 1, 0], [1, 1, 1, 1]]
Теперь понимание динамического программирования стало чуть лучше