# Dynamic Programming --- CS 137 // 2021-11-08 ## Administrivia - Daily exercises 9 and 10 + Short exercises on graphs + DE9 was due today, but I forgot to discuss it in class on Wednesday, so I am allowing you to turn it in late + We will go over both DE9 and DE10 on Wednesday # Questions ## ...about anything? ## Principle of Graph Problems - Whenever possible, **build graphs**, not algorithms # Greedy Algorithms ## Greedy Algorithms - Recently we've looked at several **greedy** algorithms - Greedy algorithms are of the "best thing first" variety and can be used to solve a wide variety of problems - But it doesn't work for all problems... + We will see a few later in the course # Dynamic Programming ## Dynamic Programming - Dynamic programming is a technique for improving the efficiency of certain recursive algorithms - The runtime improvement is due to cleverly caching the results of sub-problems to avoid solving them multiple times ## Why the name? - "Programming" $\ne$ "writing programs" + Term was borrowed from schedule optimization and akin to "putting events into slots" + Or "filling in a table" - "Dynamic" just means filling in the table *over time* ## What's the method? 1. Find a recursive solution to a problem 2. Determine the order to solve sub-problems 3. Populate a table of all the sub-problems, avoiding the need to solve sub-problems multiple times ## Fibonacci Numbers - A classic recursive sequence is Fibonacci $$ F_n = \begin{cases} 1, &\text{ if }n=0\text{ or }1\\\\ F_{n-1} + F_{n-2}, &\text{ otherwise} \end{cases} $$ ## Recursive Implementation - Implementing Fibonacci is trivial: ```py def fib(n): if n == 0 or n == 1: return 1 else: return fib(n-1) + fib(n-2) ``` ## Visualization ![Fibonacci Visualization](../../assets/images/fibonacci.png) ## Runtime? - What is the runtime? + Exponential! - How many **distinct** calls to `fib` are made? + Only $n$---which is not very many! - How can we speed this up? ## Memoization - One trivial way to give some performance improvement to a recursive algorithm is by creating a **cache** of recently computed results - This is called **memoization** - Memoization is technically not dynamic programming, but it is a quick way to get some performance improvements ## Memoized Fibonacci - Here is the cached version of Fibonacci: ```py cache = {0:1, 1:1} def fib(n): if n not in cache: cache[n] = fib(n-1) + fib(n-2) return cache[n] ``` - Runtime? + Only $n$ unique calls, so expected $O(n)$-time ## Python Memoization - Memoization is so common, some programming languages include libraries that automate it for you: ```py from functools import cache @cache def fib(n): if n == 0 or n == 1: return 1 else: return fib(n-1) + fib(n-2) ``` ## Fibonacci with Dynamic Programming 1. Create array $A$ with $n+1$ slots 2. Set $A[0] = 1$ and $A[1] = 1$ 3. For $i = 2\ldots n$: 1. $A[i] = A[i-1] + A[i-2]$ 4. Return $A[n]$ ## Fibonacci with DP - Notice that our DP solution implements a recursive algorithm, yet it makes no recursive calls - Instead, it is "filling in a table dynamically" in such a way that each distinct sub-problem is only evaluated once - It is also *guaranteed* $O(n)$-time + With memoization it was *expected* $O(n)$-time # Word Segmentation ## Word Segmentation - Another problem that is commonly used in natural language processing is *word segmentation* - **INPUT:** A string of letters $x\in\\{a,\ldots,z\\}^\*$ and a dictionary of words with $O(1)$ lookup time - **OUTPUT:** `True` if $x$ is the concatenation of a sequence of legal words in the dictionary and `False` otherwise ## Word Segmentation - For example: + `"thequickbrownfox"` can be segmented into `["the", "quick", "brown", "fox"]` - However, the following has no segmentation: + `"abcdefg"` ## Recursive WS Solution - Define $WS(x)$ to be the following algorithm: 1. If $|x| = 0$, return `True` 2. Otherwise, for $j = 1\ldots |x|$: 1. If $WS(x_{1\ldots j})$ and $x_{j+1\ldots |x|} \in \text{dictionary}$ 1. Return `True` 3. Return `False` ## WS Analysis - What is the running time? + Exponential! - How many distinct calls to $WS$? + Only $n$ + $x_{1\ldots j}$ for $j = 1\ldots n$ - Let's try a DP approach! ## DP WS Solution 1. Initialize array VALID[0] = True 2. For $i = 1\ldots |x|$: 1. VALID[i] = False 2. For $j=0\ldots i-1$: 1. If VALID[j] and $x_{j+1\ldots i} \in \text{dictionary}$, then set VALID[i] = True and break inner loop 3. Return VALID[$|x|$] ## DP WS Analysis - Running time? + $O(n^2)$