The best course on Dynamic Programming happened to be in JavaScript. Because Python has some particularities in how it handles data structures and organises stack frames, here is a Python version of the functions explained in the course.
The structure of this post mirrors the course video, with all relevant links with timestamps included. First, there is a brute force solution for the stated problem and then an improved memoized version. I use the original function names in sub-headers for easy reference. However, in description and code, I stick to Python conventions for naming and implementation as much as possible.
Quick reference
- Fibonacci problem
- Grid traveller problem
- canSum problem
- howSum problem
- bestSum problem
- canConstruct problem
- countConstruct problem
- allConstruct problem
- fib tabulation
- Grid traveller tabulation
- canSum tabulation
- howSum tabulation
- bestSum tabulation
- canConstruct tabulation
- countConstruct tabulation
- allConstruct tabulation
Fibonacci problem. Memoization
Problem: write a function fib(n) that takes in a number as an argument. The function should return the n-th number of the Fibonacci sequence. The 0th number of the sequence is 0. The 1st number of the sequence is 1. To generate the next number of the sequence, we sum the previous two.
Fibonacci explanation in the course. And corresponding Python code:
Python implementation notes for memoized solution. Note this empty dict memo object in function parameters. Usually, mutable types as defaults in function parameters is a bad idea. This is because when Python loads function into memory, it assigns a pointer to this object (in this case, memo dictionary). So it is always the same dictionary, as it points to the same location in memory. And it will aggregate values across all function calls. In some cases, this can be undesirable, but in the case of memoizing Fibonacci numbers, this is useful, so we can initiate an empty dictionary as a memo object. Another worth mentioning point is that we do not need to pass memo object in subsequent recursive calls, as it is already in the stack.
Grid traveller problem
Problem: Say that you are a traveller on a 2D grid. You begin in the top-left corner and your goal is to travel to the bottom-right corner. You only move down or right.
In how many ways can you travel to the goal on a grid with dimensions m * n?
Grid traveller problem explanation and Python implementation:
Python implementation note for memoized solution. In Python concatenating string and integer will result in “TypeError: unsupported operand type(s) for +: ‘int’ and ‘str'”. To avoid this we cast integer into string using syntax str(int)
canSum problem
Problem: write a function can_sum(target_sum, numbers) that takes in a target_sum and a list of numbers as arguments. The function should return a boolean indicating whether or not it is possible to generate the target_sum using numbers from the list. You may use an element of the list as many times as needed. You may assume that all input numbers are nonnegative.
canSum problem explanation and Python code:
Python implementation notes for memoized solution. Keeping in mind how Python treats mutable defaults in function parameters, it is clear that we cannot use an empty dictionary as the default parameter for the memo object in this version of the problem. As all inputs will be different, so different will be the ways to reach target_sum. For this program to work, we need to pass an empty memo dictionary for each function call. Thus the memo dictionary will be constructed individually for given parameters and will not be shared across calls.
howSum problem
Problem: write a function how_sum(target_sum, numbers) that takes in a target_sum and a list of numbers as arguments. The function should return a list containing any combination of elements that add up to exactly the target_sum. If there is no combination that adds up to the target_sum, then return None. If there are multiple combinations possible, you may return any single one. You may use an element of the list as many times as needed. You may assume that all input numbers are nonnegative.
howSum problem explanation and Python code:
Python implementation notes for memoized solution. As with can_sum() problem, we cannot pass empty dictionary as default parameter for memo object. It should be defined separately for each function call.
bestSum problem
Problem: write a function best_sum(target_sum, numbers) that takes in a target_sum and a list of numbers as arguments. The function should return a list containing the shortest combination of numbers that add up to exactly the target_sum.
If there a tie for the shortest combination, you may return any one of the shortest.
Python code for the bestSum problem:
Python implementation notes for memoized solution. In this problem we need to explicitly create a copy of the remainder combination variable.
canConstruct problem
Problem: write a function can_construct(target, word_bank) that accepts a target string and a list of strings. The function should return a boolean indicating whether or not the target can be constructed by concatenating elements of the word_bank list. You may reuse elements of the word_bank as many times as needed.
Python implementation of canConstruct problem
As in previous examples we need to pass an empty dict as memo to every function call.
countConstruct problem
Problem: write a function count_construct(target, word_bank) that accepts a target string and a list of strings. The function should return the number of ways that the target can be constructed by concatenating elements of the word_bank list. You may reuse elements of the word_bank as many times as needed.
Python implementation of countConstruct problem
allConstruct problem
Problem: write a function all_construct(target, word_bank) that accepts a target string and a list of strings. The function should return a 2D list containing all the ways that the target can be constructed by concatenating elements of the word_bank list. Each element of the 2D list should represent one combination that constructs the target. You may reuse elements of the word_bank as many times as needed.
Python implementation of allConstruct problem
Python implementation notes. The catch here is in implementing target_ways. We need to copy all sub-lists in a list. However, using list.copy()
on nested lists will only copy the outer list while all sub-lists will keep pointing to the sub-lists in the original list. There are several ways to implement copy for nested lists in Python, I choose to use list comprehension with full slicing new_list = [sublist[:] for sublist in nested_list]
fib tabulation
Python implementation of Fibonacci problem using tabulation
Grid traveller tabulation
Python implementation of Grid traveller problem using tabulation.
canSum tabulation
Python implementation of canSum problem using tabulation.
howSum tabulation
Python implementation of howSum problem using tabulation.
bestSum tabulation
Python implementation of bestSum problem using tabulation
canConstruct tabulation
Python implementation of canConstruct problem using tabulation.
countConstruct tabulation
Python implementation of countConstruct problem using tabulation.
allConstruct tabulation
Python implementation of allConstruct problem using tabulation