Search code examples
pythonalgorithmmemoization

How to correctly implement memoization?


I have been spending way too much time on a programming challenge:

Given a budget and a set of prices, compute all unique combinations on how to spend the entire budget.

Example

Input

budget = 100
prices = [25, 50]

Output

[[25, 25, 25, 25], [25, 25, 50], [25, 50, 25], [50, 25, 25], [50, 50]]

I have implemented a brute force solution in Python that works correctly:

def spend_the_budget(budget: int, prices: list) -> list:
    if budget == 0:
        return [[]]
    if budget < 0:
        return None

    combinations = []
    for price in prices:
        remainder = budget - price
        remainder_combinations = spend_the_budget(remainder, prices)
        if remainder_combinations is not None:
            for remainder_combination in remainder_combinations:
                remainder_combination += [price]
                combinations.append(remainder_combination)
    return combinations

However, this has obviously exponential time complexity and therefore doesn't scale in an acceptable fashion. Therefore I'd like to add memoization but can't seem to get it to work:

def spend_the_budget_memoized(budget: int, prices: list, memo: dict = {}) -> list:
    if budget in memo:
        return memo[budget]
    if budget == 0:
        return [[]]
    if budget < 0:
        return None

    combinations = []
    for price in prices:
        remainder = budget - price
        remainder_combinations = spend_the_budget_memoized(remainder, prices, memo)
        if remainder_combinations is not None:
            for remainder_combination in remainder_combinations:
                remainder_combination += [price]
                combinations.append(remainder_combination)
    memo[budget] = combinations
    return combinations

Weirdly enough however, this yields an incorrect result and I just can't wrap around my head as to what I did wrong here:

Output

[[25, 25, 25, 50, 25, 25, 50], [50, 25, 25, 50], [25, 25, 25, 50, 25, 25, 50], [25, 25, 25, 50, 25, 25, 50], [50, 25, 25, 50]]

Solution

  • for remainder_combination in remainder_combinations:
                    remainder_combination += [price]
                    combinations.append(remainder_combination)
    

    When you iterate over remainder_combinations you are iterating over the same copy which is stored in memo. Use list.copy() instead,

    for remainder_combination in remainder_combinations:
                    temp = remainder_combination.copy()
                    temp += [price]
                    combinations.append(temp)