Generation of Combinatorial Objects · Application: Dynamic Programming (Optional)

Lesson 1

Nikolai Chukhin · Alexander S. Kulikov

Let us recall our recursive code for the knapsack problem, which simply enumerates all subsets.

def knapsack(capacity, weights):
    def helper(idx, items):
        if idx != len(weights):
            return max(helper(idx + 1, items), helper(idx + 1, items + [weights[idx]]))
        return sum(items) if sum(items) <= capacity else 0

    return helper(idx=0, items=[])


print(knapsack(capacity=28, weights=[17, 9, 7, 3]))

27

In each recursive call we pass two parameters: \(\texttt{idx}\) and \(\texttt{items}\). The first parameter is the index of the item we need to make a decision about at this step (whether to take it or not). The \(\texttt{items}\) parameter represents the set of items taken among the first few (more precisely, among the items with indices from zero to \(\texttt{idx}-1\)). But if we look closely, we see that what we need from \(\texttt{items}\) is just one thing — the sum of its elements. So instead of \(\texttt{items}\) we can just pass this sum! We will use the variable \(\texttt{total}\) for that.

def knapsack(capacity, weights):
    def helper(idx, total):
        if idx == len(weights):
            return total if total <= capacity else 0
        else:
            return max(helper(idx + 1, total), helper(idx + 1, total + weights[idx]))

    return helper(idx=0, total=0)


print(knapsack(capacity=28, weights=[17, 9, 7, 3]))

27