15. Dynamic Programming, Part 1: SRTBOT, Fib, DAGs, Bowling

00:57:18
https://www.youtube.com/watch?v=r4-cftqTcdI

Summary

TLDRThis lecture introduces a new section on algorithmic design with a focus on the powerful and widely-used paradigm of dynamic programming (DP). It explains the basic DP methodology using a template called SRTBOT, which helps in designing recursive algorithms efficiently. The lecture uses the Fibonacci sequence to demonstrate the transition of recursive algorithms from exponential to polynomial time complexity through memoization. Basic concepts such as recursion and memoization are clarified before moving on to more complex problems. The session includes examples such as merge sort and shortest path in a DAG, showing their applications under dynamic programming frameworks. This is followed by a detailed exploration of a new problem, modeled as a custom bowling game, illustrating the application of DP in finding optimal strategies under constraints. The lecture emphasizes on the challenges in sub-problem design in the context of dynamic programming and proposes utilizing sequence-based breakdowns like prefixes, suffixes, and substrings. The intended takeaway is to equip participants with a framework to tackle a wide variety of algorithmic problems systematically using dynamic programming.

Takeaways

  • 🔍 Dynamic programming is focused on breaking problems into smaller subproblems and solving each just once.
  • 🧮 SRTBOT template is used for designing recursive algorithms based on dynamic programming.
  • 🚀 Memoization can drastically improve algorithm efficiency by remembering past computations.
  • 📈 Fibonacci sequence is a classic problem to demonstrate dynamic programming and memoization.
  • 🛤️ DAG shortest paths can be solved using dynamic programming techniques.
  • 🎳 Custom bowling game problem showcases practical applications of dynamic programming.
  • 💡 Using sequence-based decompositions helps in defining subproblems for dynamic programming.
  • 🔄 Recursive algorithms can be transformed into more efficient ones using dynamic programming.
  • ⚖️ Correct dynamic programming usage should yield polynomial time solutions.
  • 🧠 Efficient subproblem formulation is crucial in dynamic programming.

Timeline

  • 00:00:00 - 00:05:00

    Erik Demaine introduces a new section of the course focusing on algorithmic design, particularly dynamic programming, a recursive algorithmic design paradigm. This approach allows solving complex problems by writing constant-sized code, independent of problem size, through recursion or loops. The class uses proof-by-induction and subproblem graphs to discuss algorithm design, introducing an acronym SRTBOT (Sub-problems, Relations, Topological order, Base case, Original problem, Time) to help remember the steps in recursive algorithm design. Dynamic programming builds on this with memoization, reusing previous work.

  • 00:05:00 - 00:10:00

    To avoid infinite loops in recursive algorithm calls, ensure the dependencies between subproblems are acyclic, which involves specifying a topological order. Base cases solve recursive structures, and the goal is to solve the original problem using subproblems. The course will explore dynamic programming through various examples, starting with illustrating via merge sort, though it's a divide and conquer algorithm. Merge sort involves sorting sub-arrays and using recurrence relations to solve problems iteratively, maintaining a topological order and base case while sticking to known recurrence solutions like the one in merge sort for analysis.

  • 00:10:00 - 00:15:00

    Illustrating recursion on Fibonacci numbers through a toy problem to introduce memoization, a key concept in dynamic programming. The Fibonacci calculation using recursion without memoization results in exponential time complexity due to repeated calculations. With memoization, results of subproblems are cached and reused, drastically improving efficiency to linear time by reducing redundant calculations. Explaining through drawings and examples, Demaine emphasizes rewriting recursions to check cache first before proceeding and how this significantly cuts down computation time and complexity.

  • 00:15:00 - 00:20:00

    Detailed explanation of leveraging memoization with Fibonacci recursion to optimize from exponential to linear time solutions demonstrates the utility of this technique in reducing redundant calculations. The memoization technique ensures that each subproblem is only solved once, providing significant computational savings. Discussion on how traditional assumptions about arithmetic operations being free in typical algorithm analysis are adjusted, emphasizing polynomial growth versus exponential along with explorations into merge sort's complexities, confirming it's an inefficient use of memoization.

  • 00:20:00 - 00:25:00

    Illustrating the inadequate outcome of applying recursive approaches to different problem types like merge sort, emphasizing the utility and power of dynamic programming when used appropriately. Introducing the DAG (Directed Acyclic Graph) shortest paths problem, a distinct case of reuse where algorithmic insights can bridge dynamic programming concepts applied in efficient, non-recursive manners. Highlighting dynamic programming's efficiency through examples like solving DAG shortest paths by leveraging direct edge calculations and recursion to deliver improved time complexities compared to naive methods.

  • 00:25:00 - 00:30:00

    Describing dynamic programming interpretations as recursive computations with memoization, hinting at how algorithms recursively employ depth-first search structures inherently due to problem type and constraints. Through DAG shortest paths, Demaine articulates how dynamic programming captures implicit explorations, solving computational problems by systematically breaking down dependencies between given graph nodes, emphasizing the automated topological ordering ensured through memoization, effectively converging memory and computation affordably.

  • 00:30:00 - 00:35:00

    Introducing bowling as a novel computational problem to showcase dynamic programming in practice. With constraints resembling real-life problems, this example is devised to demonstrate computational efficiency, utilizing dynamic programming principles. Using simple rules and adding scoring functions, the bowling challenge is constructed as a fun analogy to algorithmic problem-solving. Demaine explains setup, criteria, and scoring, presenting the problem as a sequential challenge, which is crucial for understanding the practicality of algorithmic concepts applied in sequence analysis.

  • 00:35:00 - 00:40:00

    Demaine introduces a new unique problem setup, playing an ancient-like bowling game where values are attached to each pin, with the emphasis on maximizing score through strategic gameplay, providing an engaging classroom challenge to solve using dynamic programming. Bowling serves as a metaphorical ground for applying ideas about sequence influence and impact of actions, reflecting on problem-solving approaches that heavily rely on strategic considerations of operations order and possible outcomes, thus pushing attention towards maximizing expected outcomes using sequence logic.

  • 00:40:00 - 00:45:00

    He presents SRTBOT methodology applied to bowling problem-solving. By breaking down pins into sequential sub-problems and evaluating various solutions pathways, the key concepts of dynamic programming come into play. Demaine outlines how suffixes of sequences can serve as essential sub-problems in dynamic problem formulations. Through strategic evaluation of each pin's possible interactions and the subsequent effect on the configuration, outcomes are optimized using local brute force creatively bounded by predefined subproblem scopes, avoiding exponential complexity growth.

  • 00:45:00 - 00:50:00

    The explanation extends to implementing dynamic programming solutions both recursively and iteratively (bottom-up), highlighting the procedural transformation from theoretical model to practical coding. Through structured conditional logic, explicit ordering, and simplicity in mathematical expressions (like max/min functions), algorithmic joys are shared, linking dynamic model adjustments and real-world coding habits (e.g., bottom-up implementation mimicking memoization). Demaine emphasizes computational thinking where problems are visualized as sequence-bound, favoring early breakdowns and task automation.

  • 00:50:00 - 00:57:18

    Concluding, he reiterates dynamic programming's strength in handling exponential scenarios by structuring computations around sequence-based sub-problems, advocating for preemptive identification of subproblem boundaries to leverage memorized knowledge efficiently. This method optimizes exploration breadth, maintaining control over planning spaces in typical algorithmic fashion, reinforcing the beauty and utility of recursive thinking across varied domains. Upcoming lectures promise further examples to deepen understanding and broaden application spectrum within this refined algorithmic scaffold.

Show more

Mind Map

Mind Map

Frequently Asked Question

  • What is dynamic programming?

    Dynamic programming is an algorithmic design paradigm focused on solving complex problems by breaking them down into smaller, simpler subproblems and solving each of those subproblems just once, storing their solutions - typically using a recursive approach enhanced by memoization.

  • What is the SRTBOT template?

    The SRTBOT stands for Sub-problems, Relations, Topological order, Base case, Original problem, and Time, serving as a template to design recursive algorithms based on dynamic programming.

  • What is memoization in dynamic programming?

    Memoization is a technique used in dynamic programming to cache the results of expensive function calls and return the cached result when the same inputs occur again.

  • How does dynamic programming transform a recursive algorithm?

    The simplest transformation involves checking if a sub-problem has already been solved using memoization and reusing the result instead of re-computing it, thus avoiding redundant calculations.

  • Which example was used to demonstrate memoization?

    Fibonacci sequence problem was used to illustrate memoization. The naive recursive solution takes exponential time, but applying memoization transforms it into a polynomial time complexity problem.

  • Can dynamic programming be applied to shortest path problems?

    Yes, by computing the shortest paths using a recursive relation for every vertex to calculate the shortest distance in a directed acyclic graph (DAG), a form of DP can be employed.

  • What real-world-like problem was discussed that uses dynamic programming?

    The lecture discusses a variant bowling game where each pin has a different score and the goal was to maximize scores within given rules, showcasing practical application of dynamic programming.

  • What is the time complexity difference in solving Fibonacci with and without memoization?

    The running time for the naive recursive Fibonacci sequence calculation is exponential, while with memoization applied, it becomes linear.

  • What is the expected time complexity when using dynamic programming correctly?

    Linear time, as each subproblem is typically solved once, reducing overall computation times by avoiding redundancies through memoization.

  • What are common sub-problem formulations in dynamic programming?

    Prefixed and suffixed are generally efficient with fewer sub-problems to analyze, while substring decompositions provide detailed granularity useful in complex problem definitions.

View more video summaries

Get instant access to free YouTube video summaries powered by AI!
Subtitles
en
Auto Scroll:
  • 00:00:00
    [SQUEAKING] [RUSTLING] [CLICKING]
  • 00:00:04
  • 00:00:12
    ERIK DEMAINE: All right, welcome back to 006.
  • 00:00:15
    Today we start a totally new section of the class.
  • 00:00:18
    Up till now, we've mostly been showing
  • 00:00:21
    you really cool and powerful algorithms, sorting algorithms,
  • 00:00:25
    graph algorithms, data structures, trees, lots
  • 00:00:28
    of good stuff that you can apply to solve tons
  • 00:00:30
    of algorithmic problems, either by reducing to the data
  • 00:00:34
    structures that we showed you, or reducing to graph problems
  • 00:00:38
    that we showed you, or by modifying
  • 00:00:40
    those algorithms a bit.
  • 00:00:41
    Today we're going to start a new section on algorithmic design--
  • 00:00:45
    how to, from scratch, come up with a polynomial time
  • 00:00:49
    algorithm to solve a problem.
  • 00:00:50
  • 00:00:52
    And in particular, we're going to talk
  • 00:00:54
    about a algorithmic design paradigm called
  • 00:00:57
    dynamic programming, which is extremely powerful.
  • 00:01:00
    It's probably the most powerful algorithmic design paradigm.
  • 00:01:02
    Very general.
  • 00:01:03
    Can solve lots of problems.
  • 00:01:06
    It's a particular type of recursive algorithm design.
  • 00:01:10
    And in general, this class--
  • 00:01:14
    all of algorithms-- is about recursive algorithm design
  • 00:01:17
    at some level, because we want to write
  • 00:01:19
    constant-sized pieces of code that solve
  • 00:01:22
    problems of arbitrary size.
  • 00:01:23
    We have some problem size n and we're
  • 00:01:25
    trying to write 100 lines of code or whatever,
  • 00:01:27
    some constant amount that doesn't depend on the problem
  • 00:01:30
    size.
  • 00:01:30
    We have one algorithm that solves
  • 00:01:32
    all instances of the problem.
  • 00:01:34
    And so we have to write code that is recursive or uses loops
  • 00:01:39
    or somehow reuses the instructions
  • 00:01:41
    that we give the computer.
  • 00:01:43
    And you may know you can convert any algorithm based
  • 00:01:46
    on loops into an algorithm using recursion.
  • 00:01:48
    And we're going to take the recursive view today,
  • 00:01:51
    in particular because it fits very
  • 00:01:53
    well with our proof-by-induction technique, which we've
  • 00:01:56
    used throughout this class, but also because it gives us
  • 00:02:00
    some structure on how different subproblems relate in something
  • 00:02:04
    called a subproblem graph, that we'll be talking about today.
  • 00:02:08
    And so we're going to start out with, in general, how do we
  • 00:02:11
    design recursive algorithms?
  • 00:02:13
    That's sort of the overall, encompassing everything.
  • 00:02:16
    We have thought very hard to come up
  • 00:02:18
    with a cool acronym for this paradigm which we invented
  • 00:02:21
    called SRTBOT--
  • 00:02:23
    thanks, Jason.
  • 00:02:25
    And so we'll talk-- it's not actually for sorting.
  • 00:02:27
    It's just an acronym for sub-problems, relations,
  • 00:02:33
    topological order, base case, original problem, and time.
  • 00:02:39
    But it's an acronym that will help you remember
  • 00:02:42
    all the steps you need in order to specify
  • 00:02:44
    a recursive algorithm.
  • 00:02:46
    And dynamic programming is going to build
  • 00:02:47
    on this template by adding one new idea called
  • 00:02:52
    memoization, which is just the idea of reusing work
  • 00:02:55
    that you've done before.
  • 00:02:57
    And that's going to let us solve tons of problems.
  • 00:03:00
    And let's see.
  • 00:03:02
    I don't-- let's get into it.
  • 00:03:05
    So we'll start out today with SRTBOT.
  • 00:03:08
    So here is SRTBOT down the column here.
  • 00:03:13
    This is a recursive algorithm design paradigm.
  • 00:03:16
    And in general, what we're going to do
  • 00:03:18
    is take the problem that we actually want to solve
  • 00:03:20
    and split it up into lots of possible sub problems.
  • 00:03:24
    And so the first part is to define what
  • 00:03:26
    the heck are the subproblems.
  • 00:03:27
    In general, we'll want some polynomial number of them.
  • 00:03:30
    But it's pretty open-ended what these look like.
  • 00:03:34
    And the hardest part, usually, in defining
  • 00:03:37
    a recursive algorithm is figuring out
  • 00:03:39
    what the sub problems should be.
  • 00:03:40
    Usually they're related to the problem you want to solve.
  • 00:03:42
    Often the problem you want to solve--
  • 00:03:45
    this is actually near the last step--
  • 00:03:46
    the original problem you're trying to solve
  • 00:03:48
    is often one of these sub problems.
  • 00:03:51
    And then you use the smaller sub problems
  • 00:03:53
    in order to build up the final, original problem.
  • 00:03:56
    But sometimes at the end, you need
  • 00:03:58
    to take a bunch of subproblems and combine them
  • 00:04:00
    into your original problem.
  • 00:04:01
    You can think-- one analogy you can think of here
  • 00:04:04
    is divide and conquer algorithms,
  • 00:04:06
    which also had this kind of style.
  • 00:04:09
    But more generally, we're going to relate different sub problem
  • 00:04:12
    solutions with some recursive structure-- some recurrence
  • 00:04:17
    relation.
  • 00:04:19
    This is just a recursive algorithm
  • 00:04:21
    that defines how to solve one problem in terms
  • 00:04:24
    of smaller sub-problems for some notion of smaller.
  • 00:04:28
    And this is given by the topological order.
  • 00:04:31
    So if we think of the subproblems as a graph
  • 00:04:34
    and we draw an edge between--
  • 00:04:37
    so the vertices of the graph are sub problems.
  • 00:04:40
    The edges are the dependencies between those subproblems.
  • 00:04:42
    Then what we'd like is the topological ordering,
  • 00:04:45
    the topological sort problem we talked about in the context
  • 00:04:48
    of DFS or DAG shortest paths.
  • 00:04:51
    What we would like is that the subproblems and the calls--
  • 00:04:56
    the recursive calls between them in this recursive relation--
  • 00:04:59
    forms a DAG.
  • 00:05:00
    We want it to be acyclic, otherwise
  • 00:05:02
    you have an infinite loop in your recursive calls.
  • 00:05:05
    If you have a cycle, you'll never terminate.
  • 00:05:09
    And so to make sure that these dependencies
  • 00:05:12
    between subproblems given by this recurrence relation
  • 00:05:15
    is acyclic, one way to do that is
  • 00:05:18
    to specify a topological order.
  • 00:05:20
    Or you could prove it some other way.
  • 00:05:22
    But often it's just a for loop to say, just do it
  • 00:05:25
    in this order.
  • 00:05:27
    Then of course any recursive structure needs base cases.
  • 00:05:31
    So that's a useful step not to forget.
  • 00:05:33
    We want to solve the original problem using these sub
  • 00:05:35
    problems.
  • 00:05:36
    And then we analyze a running time at the end.
  • 00:05:38
    So six easy steps.
  • 00:05:42
    Actually, the hardest ones are these two,
  • 00:05:43
    which are interrelated.
  • 00:05:45
    And what we're going to see over the next four lectures--
  • 00:05:49
    this is the first of four lectures
  • 00:05:50
    on dynamic programming-- is lots of examples of applying
  • 00:05:53
    this paradigm over and over together with the memoization
  • 00:05:56
    idea, which we'll get to soon.
  • 00:05:58
    Let's see an example first of an algorithm we've already seen,
  • 00:06:03
    which is merge sort, so a divide and conquer algorithm,
  • 00:06:05
    phrased with this structure of SRTBOT.
  • 00:06:09
    So for the sub problems--
  • 00:06:11
    so our original problem is to sort the elements of A.
  • 00:06:14
    And some sub-problems that we solve along the way are sorting
  • 00:06:18
    different sub-arrays of A. So for every--
  • 00:06:21
    well, not for every i and j, but for some i and js,
  • 00:06:24
    we sort the items from i up to j minus 1.
  • 00:06:28
    So I'm going to define that subproblem to be s of ij.
  • 00:06:32
    So this is something that I might want to solve.
  • 00:06:34
    The original problem that I want to solve
  • 00:06:37
    is s of 0 comma n, where n is the length of the array.
  • 00:06:40
    So that's what I actually care about in the end.
  • 00:06:42
    But we're going to solve that by writing it recursively
  • 00:06:45
    in terms of sorting different sub-arrays as follows.
  • 00:06:49
    This is the recurrence relation.
  • 00:06:51
    I've written it very simply here.
  • 00:06:53
    Of course, there's a merge algorithm,
  • 00:06:55
    which is somewhat complicated.
  • 00:06:57
    But as we saw the two finger linear time merge algorithm,
  • 00:07:00
    given two sorted arrays--
  • 00:07:04
    so this is supposed to be the sorted array
  • 00:07:06
    version of the items i through m. m
  • 00:07:09
    is the middle element between i and j
  • 00:07:11
    and the sorted array of the items from m up to j.
  • 00:07:15
    If we merge those, that gives us the sorted array
  • 00:07:18
    from i up to j.
  • 00:07:21
    And that's exactly what merge sort does.
  • 00:07:24
    So in general, this relation is just
  • 00:07:28
    some algorithm for if you're given the solutions
  • 00:07:33
    to some smaller subproblems, how do I solve the subproblem
  • 00:07:38
    that I want to solve?
  • 00:07:41
    And so we need to make sure that this problem is bigger
  • 00:07:46
    than the ones that we recursively call on
  • 00:07:48
    and that we don't get an infinite cyclic loop
  • 00:07:51
    of recursions.
  • 00:07:51
    And here our valid topological order
  • 00:07:53
    is to say, solve these problems in order
  • 00:07:57
    where j minus i-- the length of the sub-array-- is increasing.
  • 00:08:02
    And then you can check because m is strictly between i and j.
  • 00:08:05
    As long as we're not in a base case, then we know we can--
  • 00:08:11
    these subarrays will be smaller than this one.
  • 00:08:14
    And so this increasing order gives us
  • 00:08:15
    a valid topological order on all of the problems, all
  • 00:08:19
    the subproblems.
  • 00:08:20
    We have a base case, which is if we
  • 00:08:22
    don't want to sort anything, that's the empty array,
  • 00:08:25
    or at least in the original problem.
  • 00:08:27
    And then running time is--
  • 00:08:29
    I mean, there's no better way to solve it than the recurrence
  • 00:08:32
    that we already saw how to solve.
  • 00:08:34
    So this is just another way to think of n log n
  • 00:08:36
    merge sort in this labeled framework of SRTBOT.
  • 00:08:41
    Let's get to another problem that
  • 00:08:44
    does not fit recursion so well.
  • 00:08:48
    But we can make it better.
  • 00:08:51
    So this is-- we're going to start
  • 00:08:53
    with a very simple problem, which
  • 00:08:54
    is computing Fibonacci numbers.
  • 00:08:59
    It's really just a toy problem to illustrate a very powerful
  • 00:09:02
    idea, which is memoization.
  • 00:09:06
    So the problem I'm interested in is I'm
  • 00:09:08
    given a particular number, n.
  • 00:09:10
    And I want to compute the nth Fibonacci number.
  • 00:09:14
    And in case you forgot, the nth Fibonacci number
  • 00:09:17
    is given by this recurrence. fn is fn minus 1 plus fn minus 2
  • 00:09:22
    with base case, let's say, f1 equals f2 equals 1.
  • 00:09:26
  • 00:09:29
    And so we'd like to compute this.
  • 00:09:30
    This seems-- this is a recurrence.
  • 00:09:33
    So it seems very natural to write it
  • 00:09:36
    as a recursive algorithm.
  • 00:09:37
    So let's try to do it.
  • 00:09:38
    We start with what are the sub problems.
  • 00:09:41
    The obvious sub problems are just
  • 00:09:45
    the various Fibonacci numbers, f i for i between 1 and n.
  • 00:09:55
    So there are n of these sub problems.
  • 00:09:57
  • 00:10:00
    Cool.
  • 00:10:01
    Let's see.
  • 00:10:02
    We want a relation between them.
  • 00:10:04
  • 00:10:07
    Well, maybe just to distinguish the problems from the Fibonacci
  • 00:10:10
    numbers, let me write f of i.
  • 00:10:13
    This is a function, an algorithm we're going to define.
  • 00:10:16
    And it's defined to be--
  • 00:10:18
    the goal we're trying to get is the ith Fibonacci number
  • 00:10:22
    given i.
  • 00:10:23
    And then we can write the recurrence relation
  • 00:10:26
    on these guys, just f of i equals f of i minus 1
  • 00:10:31
    plus f of i minus 2.
  • 00:10:34
    So in other words, recursively compute those Fibonacci numbers
  • 00:10:37
    then add them together.
  • 00:10:39
    That's an algorithm.
  • 00:10:41
    Next is t for topological order.
  • 00:10:44
  • 00:10:48
    Here, of course, we just want to compute
  • 00:10:51
    these in order of increasing i from the base case is up.
  • 00:10:57
    Another way I like to write this is as a for loop for i
  • 00:11:01
    equals 1 to n.
  • 00:11:04
    We will see why.
  • 00:11:06
    But this gives an explicit order to compute these sub problems.
  • 00:11:10
  • 00:11:13
    And base case is just the same as the Fibonacci numbers,
  • 00:11:21
    but I guess I should write in parentheses.
  • 00:11:23
  • 00:11:25
    The original problem we want to solve is f of n.
  • 00:11:30
    And the time-- all right, here's where
  • 00:11:32
    things get interesting or bad.
  • 00:11:35
    So what is the running time of this recursive algorithm?
  • 00:11:39
    As I've stated it so far, the running time
  • 00:11:42
    is given by a recurrence.
  • 00:11:46
    Let's write the recurrence.
  • 00:11:47
    So in order to compute f of n, I recursively
  • 00:11:52
    compute f of i minus 1 or f of n minus 1 here.
  • 00:11:58
    And I recursively compute f of n minus 2.
  • 00:12:03
    So that will take t of n minus 2.
  • 00:12:05
    This first step will take t of n minus 1.
  • 00:12:07
    And now I need to solve this recurrence.
  • 00:12:11
    This is not a recurrence that falls to the master method.
  • 00:12:14
    It doesn't have a divided by.
  • 00:12:17
    So we have to think about it a little bit.
  • 00:12:19
    But we don't have to think about it
  • 00:12:20
    too hard, because this recurrence is
  • 00:12:23
    the same as this recurrence, which
  • 00:12:25
    is the same as this recurrence.
  • 00:12:26
    I've written it three times now.
  • 00:12:27
    And so the solution to this is the nth Fibonacci number.
  • 00:12:32
    Oh, sorry.
  • 00:12:33
    It's a little bit worse because in addition
  • 00:12:35
    to those recursions, I also spend constant time
  • 00:12:38
    to do the addition, maybe more than constant time.
  • 00:12:40
    But if we just count the number of additions we do,
  • 00:12:44
    it will be plus 1 additions.
  • 00:12:51
  • 00:12:53
    OK.
  • 00:12:54
    But this is bigger than the nth Fibonacci number.
  • 00:12:58
    And if you know anything about Fibonacci numbers,
  • 00:13:01
    they grow exponentially.
  • 00:13:03
    They're about golden ratio to the end.
  • 00:13:06
    I'm wearing golden ratio, in case you forgot the number.
  • 00:13:09
    So that's bad, because golden ratio is bigger than 1.
  • 00:13:13
    So this is exponential growth, as we know, especially
  • 00:13:16
    in this time, exponential growth is bad.
  • 00:13:18
    In algorithms, exponential growth
  • 00:13:19
    is bad, because we can only solve very small problems
  • 00:13:22
    with exponential growth.
  • 00:13:23
    Very small n.
  • 00:13:25
    So this is a terrible way to compute the nth Fibonacci
  • 00:13:28
    number--
  • 00:13:29
    exponential bad.
  • 00:13:40
    OK, so don't do this.
  • 00:13:43
    But there's a very tiny tweak to this algorithm
  • 00:13:47
    that makes it really good, which is memoization.
  • 00:13:53
    And this is a big idea.
  • 00:13:55
    It is the big idea of dynamic programming.
  • 00:13:59
  • 00:14:02
    It's a funny word, probably made up by computer scientists.
  • 00:14:07
    Instead of memorization, it's memoization,
  • 00:14:11
    because we're going to write things down in a memo pad.
  • 00:14:14
    It's the idea.
  • 00:14:16
    And it's a very simple idea, which
  • 00:14:18
    is just remember and reuse solutions to sub-problems.
  • 00:14:24
  • 00:14:35
    So let's draw the recursion tree for this recursive algorithm
  • 00:14:42
    as we've done it so far.
  • 00:14:43
    So at the top, we-- let me make a little bit of space.
  • 00:14:48
    At the top we are calling f of n.
  • 00:14:53
    And then that calls f of n minus 1 and f of n minus 2.
  • 00:14:59
    And it does an addition up here.
  • 00:15:00
    And then this calls f of n minus 2.
  • 00:15:04
    And this calls f of n minus 3.
  • 00:15:08
    This calls f of n minus 3.
  • 00:15:11
    And this calls f of n minus 4.
  • 00:15:15
    OK.
  • 00:15:16
    And we notice that this sub problem is
  • 00:15:23
    the same as this sub problem.
  • 00:15:25
    So to compute f of n minus 1, I need f of minus 3.
  • 00:15:28
    And also to compute f of n minus 2 I need f of n minus 3.
  • 00:15:32
    So why are we computing it twice?
  • 00:15:33
    Let's just do it once.
  • 00:15:36
    When we solve it, let's write it in a table somewhere.
  • 00:15:39
    And then when we need it again, we'll just reuse that value.
  • 00:15:42
    Question?
  • 00:15:42
    AUDIENCE: What about the f of n minus 2?
  • 00:15:44
    ERIK DEMAINE: f of n minus 2 is also shared.
  • 00:15:46
    So let me use a different symbol.
  • 00:15:49
    f of n minus 2 is already here.
  • 00:15:53
    So this was at the same level.
  • 00:15:54
    But we also get shared reuse between different levels.
  • 00:15:57
    In fact, I wouldn't even call f of n minus 3
  • 00:15:59
    because this whole part doesn't need
  • 00:16:02
    to be computed a second time.
  • 00:16:03
    If I already computed it here, it
  • 00:16:05
    doesn't matter which one comes first.
  • 00:16:07
    Let's say this one comes first.
  • 00:16:08
    Once this is done, I can write it down and reuse it over here.
  • 00:16:11
  • 00:16:14
    And then in here, we're going to call f of n minus three.
  • 00:16:18
    So there's still another computation of f of n minus 3.
  • 00:16:21
    When that one's done, I won't need to do this recursively.
  • 00:16:25
    OK, so magically this is going to make
  • 00:16:28
    this algorithm efficient with this very simple tweak.
  • 00:16:32
    Let me write down the tweak more explicitly.
  • 00:16:35
    I won't write code here.
  • 00:16:36
    But just describe it as a data structure.
  • 00:16:41
    So we're going to maintain our good friend, the dictionary,
  • 00:16:47
    which is abstract data type or interface.
  • 00:16:52
    We could use different data structures to do it.
  • 00:16:54
    But we're going to map some problems
  • 00:16:57
    to their solutions, at least the ones that we've solved already.
  • 00:17:01
  • 00:17:04
    And usually we can do this with just a direct access
  • 00:17:07
    array, though you could use a hash table.
  • 00:17:09
    Just get expected bounce.
  • 00:17:12
    So when we write the code for our recursive function--
  • 00:17:23
    so in general, once we have a sort bot description,
  • 00:17:26
    we can turn this into code.
  • 00:17:28
    We define f of i.
  • 00:17:30
    And it says am I in a base case?
  • 00:17:32
    If so, return this.
  • 00:17:34
    Otherwise, do this recursive call.
  • 00:17:36
    That's our recursive algorithm.
  • 00:17:38
    But we're going to do a little more now.
  • 00:17:39
    And first we're going to check whether this sub
  • 00:17:44
    problem that we're trying to solve has already been solved.
  • 00:17:49
    And if so, we return that storage solution.
  • 00:17:55
    That's the easy case, but it might not exist.
  • 00:17:59
  • 00:18:10
    And then we'll compute it in the usual way.
  • 00:18:14
    So what the code then would look like to define f of i
  • 00:18:19
    is first we check is i in our data structure.
  • 00:18:22
    This is usually called the memo.
  • 00:18:25
  • 00:18:28
    So we say, is this sub-problem-- is i in my memo data structure?
  • 00:18:33
    If so just return memo of i.
  • 00:18:34
    Done.
  • 00:18:35
    No recursion necessary.
  • 00:18:36
    Otherwise, check if I'm a base case.
  • 00:18:39
    If so, done.
  • 00:18:40
    Otherwise, recurse.
  • 00:18:42
    So recursively call f of i minus 1 and f of i minus 2.
  • 00:18:46
    And in this recursion, we can see
  • 00:18:48
    that after we call f of i minus 1, in fact,
  • 00:18:50
    it will have already computed f of i minus 2.
  • 00:18:52
    So while this call is recursive, this one
  • 00:18:55
    will immediately terminate because i minus 2
  • 00:18:57
    will already be in the memo table.
  • 00:18:59
    And so if you think about what happens, in fact,
  • 00:19:03
    we'll just have recursion down the left branch of this thing.
  • 00:19:07
    And all the right branches will be free.
  • 00:19:09
    We can just look things up in the memo table.
  • 00:19:12
    So what is the overall running time?
  • 00:19:14
    For Fibonacci, this should be order n.
  • 00:19:25
    Why is it order n?
  • 00:19:27
    This is number of additions.
  • 00:19:28
  • 00:19:31
    Come back to that in a second.
  • 00:19:33
  • 00:19:36
    In general, the way to analyze an algorithm
  • 00:19:39
    like this that uses memoization is we just
  • 00:19:41
    count how many different sub-problems are there?
  • 00:19:43
    Because once we solve the sub-problem,
  • 00:19:45
    we will never solve it again.
  • 00:19:46
    That's the whole idea of a memo table.
  • 00:19:48
    So we will solve each sub-problem at most once.
  • 00:19:51
    And so we just need to count, how much time does it take
  • 00:19:54
    to solve every sub-problem?
  • 00:19:55
    And here you can see it's constant.
  • 00:19:59
    Either it's a base case and it takes constant time
  • 00:20:01
    or we recursively call these things.
  • 00:20:04
    But those are different sub-problems.
  • 00:20:06
    So we're going to count those later.
  • 00:20:08
    And then the work that's actually
  • 00:20:09
    done by this recurrence is a single addition.
  • 00:20:12
    So in fact, it's n additions.
  • 00:20:14
    To compute fn would be exactly n additions.
  • 00:20:20
    So it turns out to be very nice closed form in this case.
  • 00:20:24
    It should be exactly n sub problems to compute f of n
  • 00:20:29
    because we started as dot at 1.
  • 00:20:32
    And each one has one additional--
  • 00:20:35
    I guess not the base case.
  • 00:20:37
    Maybe n minus 2.
  • 00:20:39
    OK.
  • 00:20:40
    Definitely order n.
  • 00:20:43
    Now, there's this one subtlety which--
  • 00:20:46
    let's forget about dynamic programming for a moment
  • 00:20:48
    and go back to good old lecture one and two,
  • 00:20:51
    talking about the word ram model of computation.
  • 00:20:55
    A question here that usually doesn't matter in this class.
  • 00:20:58
    Usually we assume additions take constant time.
  • 00:21:01
    And we usually do that because it's usually true.
  • 00:21:04
    And in general, our model is the w bit additions--
  • 00:21:09
    where w is our machine word size--
  • 00:21:12
    takes constant time.
  • 00:21:13
  • 00:21:19
    But for this problem and this problem only,
  • 00:21:22
    pretty much, for Fibonacci numbers,
  • 00:21:24
    I happen to know that the Fibonacci
  • 00:21:25
    numbers grow exponentially.
  • 00:21:27
    So to write them down actually requires theta n bits
  • 00:21:32
    because they are some constant to the n power.
  • 00:21:35
    And so they're actually really big .
  • 00:21:38
    n is probably bigger than w.
  • 00:21:40
    Usually you think of problems that are much bigger than 64
  • 00:21:44
    or whatever your word size happens to be.
  • 00:21:46
    We do assume that w is at least log n.
  • 00:21:48
    But n is probably bigger than w.
  • 00:21:51
    It might be bigger or smaller.
  • 00:21:52
    We don't know.
  • 00:21:53
    And in general, to do an n bit addition--
  • 00:21:57
    these are n bit additions--
  • 00:22:01
    is going to take ceiling of n over w time.
  • 00:22:07
    So in the end, we will spend this times n,
  • 00:22:11
    because we have to do that, many of them,
  • 00:22:12
    which is n plus n squared over w time.
  • 00:22:18
    So a bit of a weird running time.
  • 00:22:19
    But it's polynomial, whereas this original recursive
  • 00:22:23
    algorithm was exponential here.
  • 00:22:26
    Using this one simple idea of just remembering
  • 00:22:28
    the work we've done, suddenly this exponential time algorithm
  • 00:22:31
    becomes polynomial.
  • 00:22:32
    Why?
  • 00:22:33
    Because we have few sub problems.
  • 00:22:35
    We had n sub problems.
  • 00:22:39
    And for each sub problem, we could write a recurrence
  • 00:22:42
    relation that if we already knew the solutions to smaller sub
  • 00:22:46
    problems, we could compute this bigger
  • 00:22:48
    problem very efficiently.
  • 00:22:50
    This happened to be constant time or constant additions.
  • 00:22:56
    n over w time.
  • 00:22:57
    But as long as this is polynomial
  • 00:22:59
    and this is polynomial, we're happy,
  • 00:23:02
    because we have this nice formula that the time it takes
  • 00:23:07
    is, at most, the sum over all sub problems of the relation
  • 00:23:15
    time.
  • 00:23:15
  • 00:23:18
    So I'm referring to sub problems, like a number of them
  • 00:23:23
    and the time it takes to evaluate this,
  • 00:23:26
    ignoring the recursive calls.
  • 00:23:28
    That's important.
  • 00:23:28
    This is the non recursive part.
  • 00:23:33
  • 00:23:38
    In the notes, I call this non-recursive work.
  • 00:23:40
  • 00:23:45
    So this formula gives us a way to bound
  • 00:23:49
    the running time of one of these algorithms
  • 00:23:52
    if we use memoization.
  • 00:23:54
    Without memoization, this is not true,
  • 00:23:55
    Fibonacci to exponential time.
  • 00:23:57
    But if we add memoization, we know that we only
  • 00:23:59
    solve each sub-problem once.
  • 00:24:01
    And so we just need to see, for each one,
  • 00:24:03
    how much did it cost me to compute it,
  • 00:24:05
    assuming all the recursion work is free,
  • 00:24:07
    because that's already taken into account by the summation.
  • 00:24:11
    So in particular, this summation is at most
  • 00:24:12
    the number of sub-problems times the time per sub-problem,
  • 00:24:15
    which in this case was order n.
  • 00:24:17
    We could try to apply that analysis to merge sort,
  • 00:24:20
    because after all, this is also a recursive algorithm.
  • 00:24:23
    It happens to not benefit from memoization.
  • 00:24:25
    But we could throw in memoization.
  • 00:24:27
    It wouldn't hurt us.
  • 00:24:29
    But if you think about the call graph here,
  • 00:24:31
    which is like s of 0 m, which calls s of m--
  • 00:24:38
    0 n over 2 and o of n over 2n and so on.
  • 00:24:45
    It has the same picture, but there's actually
  • 00:24:47
    no common substructure here.
  • 00:24:49
    You'll never see a repeated sub-problem,
  • 00:24:51
    because this range is completely disjoined from this range.
  • 00:24:55
    But you could throw in memoization
  • 00:24:56
    and try to analyze in the same way
  • 00:24:58
    and say, well, how many sub-problems are there?
  • 00:25:01
    It looks like there's n choices for i and not quite n choices
  • 00:25:07
    but it's at most n squared different choices.
  • 00:25:11
    In fact, it's the triangular number sum of i equals 1 to n
  • 00:25:16
    of i, different possible choices for inj.
  • 00:25:20
    But this is theta n squared sub-problems,
  • 00:25:24
    which seems not so good.
  • 00:25:27
    And then how much time are we spending per sub problem?
  • 00:25:29
    Well, to solve s of ij, we have to merge
  • 00:25:33
    about that many elements.
  • 00:25:35
    We know merge takes linear time.
  • 00:25:36
    And so this takes theta j minus i time to evaluate.
  • 00:25:43
    And so what we'd like to do is sum over all the sub
  • 00:25:46
    problems of j minus i.
  • 00:25:48
    This is the not triangular number
  • 00:25:51
    but the tetrahedral number, I guess.
  • 00:25:53
    And so we end up that the running time
  • 00:25:56
    is, at most, n cubed.
  • 00:25:58
  • 00:26:00
    Great.
  • 00:26:02
    So it's true that n log n is less than or equal to n cubed,
  • 00:26:05
    but obviously not terribly useful.
  • 00:26:07
    This algorithm by the way we already know how to analyze it
  • 00:26:11
    is, indeed, n log n.
  • 00:26:12
    And the running time turns out to be theta n log n.
  • 00:26:17
    So sometimes this equation is not what you want to use.
  • 00:26:20
    But often it's good enough.
  • 00:26:22
    And especially if you just want to get
  • 00:26:23
    a polynomial upper bound, then you
  • 00:26:25
    can try to optimize it later.
  • 00:26:27
    This will give you a polynomial upper
  • 00:26:28
    bound as long as the number of sub-problems is polynomial
  • 00:26:31
    and the time per sub-problem is polynomial.
  • 00:26:34
    And indeed, n cubed is polynomial.
  • 00:26:35
    It's not a great polynomial, but this is an alternate way
  • 00:26:39
    to analyze merge sort.
  • 00:26:40
    Obviously don't do this for merge sort.
  • 00:26:43
    But it illustrates the technique.
  • 00:26:47
    Good so far?
  • 00:26:49
    Any questions?
  • 00:26:51
    All right.
  • 00:26:52
    Let me remember where we are.
  • 00:26:57
    Cool.
  • 00:26:58
    So the next thing I'd like to do is show you one more algorithm
  • 00:27:02
    that we've already seen in this class that fits very
  • 00:27:04
    nicely into this structure--
  • 00:27:07
    arguably is a dynamic program--
  • 00:27:09
    and that is DAG shortest paths.
  • 00:27:13
    So just to close the loop here, when I say dynamic programming,
  • 00:27:17
    I mean recursion with memoization.
  • 00:27:21
    I mean, we take--
  • 00:27:24
    we write a recursive piece of code,
  • 00:27:28
    which is like def f of some args,
  • 00:27:32
    some sub-problem specification.
  • 00:27:38
    We check is the problem in the memo table?
  • 00:27:44
    If so, return memo of sub-problem.
  • 00:27:52
  • 00:27:56
    And otherwise check if it's a base case
  • 00:28:01
    and solve it if it's a base case.
  • 00:28:03
    And otherwise, write the recurrence recurse
  • 00:28:09
    via relation.
  • 00:28:11
  • 00:28:14
    And set the memo table of the sub-problem
  • 00:28:20
    to be one of those things.
  • 00:28:23
    OK, so this is the generic dynamic program.
  • 00:28:26
    And implicitly, I'm writing Fibonacci in that way.
  • 00:28:32
    And all of the dynamic programs have this implicit structure
  • 00:28:35
    where I start with a memo table which is empty
  • 00:28:41
    and I always just check if I'm in the memo table.
  • 00:28:44
    If I am, I return it.
  • 00:28:45
    Otherwise I compute according to this recursive relation
  • 00:28:50
    by recursively calling f.
  • 00:28:54
    And that's it.
  • 00:28:55
    So this is every DP algorithm is going to have that structure.
  • 00:29:00
    And it's just using recursion and memoization together.
  • 00:29:04
    OK, so now let's apply that technique
  • 00:29:06
    to think about the DAG shortest paths problem.
  • 00:29:10
    The problem was, I give you a DAG.
  • 00:29:12
    I give you a source vertex, S--
  • 00:29:15
    single source shortest paths.
  • 00:29:16
    Compute the shortest path weight from S to every vertex.
  • 00:29:20
    That's the goal of the problem.
  • 00:29:22
    And we saw a way to solve that, which is DAG relaxation.
  • 00:29:24
    I'm going to show you a different way, which turns out
  • 00:29:27
    to be basically the same, but upside down, or flipped left
  • 00:29:31
    right, depending which way you direct your edges.
  • 00:29:35
    So what are our sub-problems?
  • 00:29:37
    Well, here, actually, they're kind of spelled out for us.
  • 00:29:40
    We want to compute delta and SV for all these.
  • 00:29:42
    So that is size of these sub-problems.
  • 00:29:47
    That turns out to be enough for this overall problem.
  • 00:29:51
    And the original problem we want to solve
  • 00:29:53
    is all of the sub-problems.
  • 00:29:55
    We solve all the sub-problems, we're done.
  • 00:29:57
    And then we have--
  • 00:29:59
    I think we wrote this at some point during the DAG
  • 00:30:02
    shortest paths lecture--
  • 00:30:03
    we have a recursive relation saying
  • 00:30:05
    that the shortest way to get from s to v
  • 00:30:09
    is the minimum of the shortest path
  • 00:30:12
    to get to some vertex u plus the weight of the edge from u to v.
  • 00:30:16
    Why?
  • 00:30:16
    Because if we look at a vertex v, unless we started there,
  • 00:30:21
    we came from somewhere.
  • 00:30:23
    And so we can consider all of the possible choices
  • 00:30:27
    for the previous vertex u.
  • 00:30:30
    And if you start at s and get to v,
  • 00:30:32
    you must go through one of them.
  • 00:30:34
    And so this is finding the best way among all the choices of u.
  • 00:30:39
    What's the best way to get to u?
  • 00:30:40
    And then take the edge from u to v for all edges uv.
  • 00:30:44
    And this is adjacency minus.
  • 00:30:46
    We don't usually think of that.
  • 00:30:48
    Usually we look at adjacency plus the outgoing edges.
  • 00:30:51
    This is the incoming edges.
  • 00:30:52
    And so u is an incoming--
  • 00:30:55
    uv is an incoming edge into v. OK, if we take that minimum--
  • 00:30:59
    and of course, possible there is no way to get to v.
  • 00:31:03
    And so I'll also throw infinity into the set.
  • 00:31:06
    Take the min of that set.
  • 00:31:07
    That will give me the shortest pathway
  • 00:31:09
    in an acyclic graph from s to v. And great, this is recursive.
  • 00:31:13
    This was a sub problem.
  • 00:31:14
    These are sub problems which are smaller, I guess.
  • 00:31:19
    There's no clear notion of smaller
  • 00:31:21
    here, except we already know the clear notion of smaller
  • 00:31:25
    is the topological order of our DAG.
  • 00:31:30
    Because our graph is acyclic, we know
  • 00:31:32
    it has a topological order.
  • 00:31:33
    We know how to compute it with DFS.
  • 00:31:36
    And so that guarantees there's a topological order
  • 00:31:39
    to compute these problems.
  • 00:31:41
    And in fact, the relationship between problems
  • 00:31:46
    is exactly the given graph, G. In order
  • 00:31:50
    to compute the shortest pathway from s to v,
  • 00:31:54
    I need to know the shortest pathway from s
  • 00:31:56
    to all of the incoming vertices to v.
  • 00:31:58
    And so this is I guess in the call graph,
  • 00:32:02
    this vertex calls this vertex, but direct the edge this way
  • 00:32:07
    to say that this vertex requires--
  • 00:32:11
    this vertex needs to be computed before this one.
  • 00:32:14
    And so then I can complete them in a topological order.
  • 00:32:18
    OK, we have a base case, which is delta of ss equals 0.
  • 00:32:22
    And the running time is, again, we can use this formula
  • 00:32:26
    and say, let's just sum over all the sub problems of the non
  • 00:32:29
    recursive work in our recurrence relation
  • 00:32:32
    and so it's computing this min.
  • 00:32:34
    If I gave you these deltas for free
  • 00:32:38
    and I gave you these weights, which we know from our weight
  • 00:32:41
    data structure, how long does it take to compute this min?
  • 00:32:44
    Well, however many things there are, however
  • 00:32:46
    many numbers we're minning, which
  • 00:32:48
    is the size of the incoming adjacency list plus 1
  • 00:32:52
    for that infinity.
  • 00:32:54
    And so if you compute this sum, sum of incoming edges
  • 00:32:57
    to every vertex, that's all the edges.
  • 00:33:00
    So this is v plus e.
  • 00:33:03
    So in fact, this algorithm is morally the same algorithm
  • 00:33:09
    as the one that we saw on the DAG shortest path
  • 00:33:11
    lecture, which was compute a topological order and process
  • 00:33:18
    vertices in that order and relax edges going out from vertices.
  • 00:33:23
    So here-- so in that algorithm, we
  • 00:33:26
    would have tried to relax this edge if there was a better
  • 00:33:30
    path to v. And the first one certainly
  • 00:33:32
    is better than infinity.
  • 00:33:33
    So the first one we relax indeed.
  • 00:33:36
    The next edge, if this gave a better path from s to v,
  • 00:33:39
    then we would relax that edge and update the way here
  • 00:33:42
    and do the same here.
  • 00:33:43
    In the end, we're just computing this min in the relaxation
  • 00:33:46
    algorithm but doing it step by step.
  • 00:33:48
    In the relaxation algorithm, DAG relaxation,
  • 00:33:51
    for each incoming edge to v, we update d of e if it's better.
  • 00:33:58
    And so if you repeatedly update if you're better,
  • 00:34:02
    that ends up computing a min.
  • 00:34:04
    OK, so this is the same algorithm
  • 00:34:06
    just kind of flipped backwards.
  • 00:34:08
    A funny thing, although we wrote down
  • 00:34:11
    the topological order of the sub problem graph
  • 00:34:15
    here is the topological order of g,
  • 00:34:16
    because the sub-problem graph is g,
  • 00:34:22
    the algorithm doesn't actually have to compute one.
  • 00:34:24
    It's doing it automatically for free.
  • 00:34:27
    If you think about this algorithm,
  • 00:34:31
    generic dp algorithm, which is check
  • 00:34:34
    whether we're in a memo table.
  • 00:34:35
    If so, return.
  • 00:34:36
    Otherwise, recurse, or base case.
  • 00:34:40
    This actually is a depth-first search
  • 00:34:43
    through the sub-problem graph-- technically through the reverse
  • 00:34:46
    of the sub-problem graph.
  • 00:34:48
    If I draw an edge--
  • 00:34:51
    so from small to big--
  • 00:34:58
    so I'm just saying, I orient the edges from my smaller
  • 00:35:01
    sub-problems to the ones that need it--
  • 00:35:04
    then I'm actually depth-first searching backwards
  • 00:35:06
    in this graph because the bigger problem calls the smaller
  • 00:35:10
    problem.
  • 00:35:12
    And the memo table is serving as the "have I visited this vertex
  • 00:35:16
    already" check in DFS.
  • 00:35:18
    So this is actually a DFS algorithm.
  • 00:35:20
    Plus we're doing some computation to actually solve
  • 00:35:24
    the sub-problems we care about.
  • 00:35:27
    So implicit in this algorithm, we are doing a DFS,
  • 00:35:29
    and at the same time, we're doing this shortest path
  • 00:35:32
    computation in the finishing order of that DFS traversal
  • 00:35:37
    because all the edges are backwards.
  • 00:35:39
    This is the same as the reverse finishing order
  • 00:35:41
    if the graph is forwards.
  • 00:35:42
    So in the end, we're computing a topological order
  • 00:35:46
    because dynamic programming includes
  • 00:35:49
    in it depth first search.
  • 00:35:52
    A lot of words.
  • 00:35:54
    But it's kind of cool that this framework just
  • 00:35:57
    solves DAG shortest paths without much work.
  • 00:36:02
    I mean, we did a lot of work in shortest paths
  • 00:36:04
    to prove that this relation is true.
  • 00:36:05
    Once you know it's true, the algorithm part
  • 00:36:08
    is pretty much free.
  • 00:36:11
    You just write down SRTBOT and you're done.
  • 00:36:15
    OK.
  • 00:36:18
    This brings us to in general--
  • 00:36:24
    at this point we have seen two examples
  • 00:36:26
    of dynamic programming.
  • 00:36:27
    I guess technically merge sort you
  • 00:36:28
    could think of as a dynamic program,
  • 00:36:30
    but it doesn't actually reuse anything.
  • 00:36:31
    So it's not interesting.
  • 00:36:32
    And indeed, that gave us a really bad bound.
  • 00:36:34
    We've definitely seen DAG shortest paths and Fibonacci
  • 00:36:37
    numbers as two interesting examples.
  • 00:36:39
    And what the next remainder of this lecture and the next three
  • 00:36:43
    lectures are going to be about is more and more examples
  • 00:36:46
    of dynamic programming and how you
  • 00:36:48
    can use it to solve increasingly general problems.
  • 00:36:51
    So far, we've just solved an easy problem and a problem
  • 00:36:53
    we already knew how to solve.
  • 00:36:55
    Let's go to a new problem, which is bowling.
  • 00:37:02
    Bowling is popular in Boston.
  • 00:37:04
  • 00:37:06
    Boston likes to play candlepin bowling, which
  • 00:37:10
    is a bit unusual.
  • 00:37:12
    Today we're going to play an even more unusual bowling
  • 00:37:15
    game, one that I made up based on a bowling game
  • 00:37:19
    that Henry [INAUDIBLE] made up in 1908.
  • 00:37:22
    So ancient bowling, I'll call it,
  • 00:37:26
    or I think linear bowling is what I might call it.
  • 00:37:29
    I'll just call it bowling here.
  • 00:37:31
    And now I'm going to attempt to draw a bowling pin.
  • 00:37:34
    Not bad.
  • 00:37:36
    They might get progressively worse.
  • 00:37:39
    So imagine n identical bowling pins.
  • 00:37:42
    Please pretend these are identical.
  • 00:37:44
    And I have a ball which is approximately the same size
  • 00:37:49
    as a bowling pin.
  • 00:37:50
    These bowling pins are pretty close together.
  • 00:37:52
    I should have left a little gap here.
  • 00:37:54
    And you are a really good bowler.
  • 00:37:57
    Now, unfortunately, these bowling pins are on a line.
  • 00:38:00
    And you're bowling from way down at infinity.
  • 00:38:03
    So when you bowl, you can only hit one pin
  • 00:38:08
    or two pins or zero pins.
  • 00:38:10
    But probably you want to hit some pins.
  • 00:38:12
    So if you bowl straight out of pin,
  • 00:38:14
    you will just hit that one pin.
  • 00:38:16
    And if you bowl in the middle between two pins,
  • 00:38:20
    you will knock down--
  • 00:38:22
    that's a ball, sorry--
  • 00:38:23
    you will knock down two pins.
  • 00:38:26
    And this is your model of bowling, model of computation.
  • 00:38:30
    Now, what makes this interesting is that the pins have values.
  • 00:38:36
    Pin i has value--
  • 00:38:39
    this is obviously a toy problem, though this problem--
  • 00:38:44
    this type of bowling does go back to 1908,
  • 00:38:47
    it was also a toy problem in that setting.
  • 00:38:50
    So each of these bowling pins has some number on it,
  • 00:38:55
    let's say 1, 9, 9--
  • 00:38:57
  • 00:39:01
    I'll do a slightly more interesting example,
  • 00:39:03
    maybe another one here and a 2 and a 5 and a 5, something
  • 00:39:11
    like this.
  • 00:39:13
    OK.
  • 00:39:15
    Or maybe make it a little more interesting.
  • 00:39:17
    Let's put some negative numbers on here.
  • 00:39:19
    OK.
  • 00:39:19
    And the model-- so you're at the carnival bowling.
  • 00:39:24
    Each pin has different-- potentially different values.
  • 00:39:27
    And the model is if you hit one pin, i, then you get vi points.
  • 00:39:38
    So that's straight forward.
  • 00:39:40
    To make it interesting, when you hit two pins,
  • 00:39:43
    you get the product.
  • 00:39:45
    So if I hit two pins, it's always
  • 00:39:46
    i and i plus 1 for some I. You get vi times vi plus 1 points.
  • 00:39:53
  • 00:39:56
    This is the game you're playing.
  • 00:39:58
    And it doesn't really matter that this is a product.
  • 00:40:00
    Product is just some weird function
  • 00:40:03
    that's hard to imagine.
  • 00:40:05
    If you stare at this long enough,
  • 00:40:07
    you should convince yourself that the optimal solution
  • 00:40:10
    is probably to--
  • 00:40:11
    so, for each of these numbers, I could
  • 00:40:13
    leave it singleton or pair it with its left neighbor
  • 00:40:15
    or pair it with its right neighbor.
  • 00:40:17
    But the pairings can't overlap because once I hit a pin,
  • 00:40:19
    it's gone.
  • 00:40:20
    It's knocked over.
  • 00:40:21
    It disappears.
  • 00:40:22
    So because of these nine, which are a very high value, what
  • 00:40:26
    I'd probably like to do is hit both of them together,
  • 00:40:28
    so pair them up, because 9 times 9 is 81.
  • 00:40:32
    That's really big, much better than hitting them individually
  • 00:40:34
    or hitting 9 times 1 or 9 times 2.
  • 00:40:37
    1 and 1 is kind of funny, because it's actually better
  • 00:40:40
    to hit them individually.
  • 00:40:41
    That will give you two points, whereas if I'd pair them up,
  • 00:40:43
    I only get one point.
  • 00:40:46
    2 and minus 5, that seems bad.
  • 00:40:47
    Negative 10 points.
  • 00:40:48
    My goal is to maximize score.
  • 00:40:50
  • 00:40:56
    Do you have to hit all the pins?
  • 00:41:00
    Let's say no, you don't have to hit all the pins.
  • 00:41:02
    So I could skip the minus fives.
  • 00:41:04
    But in fact, here, because they're adjacent,
  • 00:41:07
    minus 5 times minus 5 is good.
  • 00:41:09
    That's 25 points.
  • 00:41:11
    So the optimal solution for this particular instance
  • 00:41:14
    are to hit all the pins, these positive,
  • 00:41:18
    these together, these together.
  • 00:41:19
    If I added, for example, another pin of minus 3 here,
  • 00:41:23
    I would choose not to hit that pin.
  • 00:41:25
    Good question.
  • 00:41:26
    So you just play until you are tired.
  • 00:41:29
    When you decide to stop playing, how can I maximize your score?
  • 00:41:32
    There are many variations of this game.
  • 00:41:34
    All of them-- basically any variation--
  • 00:41:37
    not literally every variation, but many,
  • 00:41:40
    many variations of this problem can all be solved quickly
  • 00:41:43
    with dynamic programming.
  • 00:41:44
    But let's solve this particular one.
  • 00:41:47
    OK.
  • 00:41:48
  • 00:41:54
    So now we're really in algorithmic design mode.
  • 00:41:58
    We need to think about SRTBOT.
  • 00:42:01
    And in particular, we need to think about what would the sub
  • 00:42:04
    problems be here?
  • 00:42:06
    And at this point, we don't have a lot of help.
  • 00:42:08
    So I should probably give you some tools.
  • 00:42:11
    If I want to solve a problem like this,
  • 00:42:13
    the input is a sequence of numbers.
  • 00:42:18
    It's a sequenced data structure.
  • 00:42:19
    Maybe it's an array of numbers, which is this v array.
  • 00:42:23
  • 00:42:26
    And let's see.
  • 00:42:28
  • 00:42:31
    A general tool for sub-problem design
  • 00:42:39
    which will cover most of the problems-- maybe all
  • 00:42:42
    of the problems that we see in this class
  • 00:42:45
    for dynamic programming.
  • 00:42:47
    Here's a trick.
  • 00:42:49
    If your input is a sequence, here are some good sub-problems
  • 00:43:03
    to consider.
  • 00:43:04
  • 00:43:13
    We could do all prefixes.
  • 00:43:15
  • 00:43:18
    So let's call the sequence x.
  • 00:43:22
    So we could do x prefix means up to a given i for all i.
  • 00:43:27
    We could do all the suffixes, x from i onward for all i.
  • 00:43:34
    Or we could do substrings, which are the consecutive items
  • 00:43:41
    from i to j.
  • 00:43:43
    I don't write subsequence here.
  • 00:43:44
    Subsequence means you can omit items in the middle.
  • 00:43:46
    So substring you have to start in some position
  • 00:43:49
    and do all the things up to j.
  • 00:43:51
    So these are nice, easy to express in Python notation.
  • 00:43:55
    And these are great, because they're polynomial.
  • 00:43:57
    If I have n things--
  • 00:43:59
    if the length of my sequence, x, is n,
  • 00:44:02
    then there are n prefixes-- technically n plus 1.
  • 00:44:05
    So let's do theta n prefixes.
  • 00:44:08
    There are theta n suffixes.
  • 00:44:10
    And there are theta n squared substrings
  • 00:44:14
    because there's n-- roughly n choices for i and j separately.
  • 00:44:19
    Sorry?
  • 00:44:21
    Sub-sequences.
  • 00:44:21
    Good.
  • 00:44:22
    Right.
  • 00:44:22
    I didn't write sub-sequences, because in fact, there
  • 00:44:24
    are exponentially many sub sequences.
  • 00:44:26
    It's 2 to the n.
  • 00:44:27
    For every item, I could choose it or not.
  • 00:44:29
    So I don't want to parameterize--
  • 00:44:31
    I don't want my sub problems to be sub sequences because that's
  • 00:44:35
    guaranteed-- well, then you're guaranteed
  • 00:44:37
    to get an exponential number of sub-problems, which is bad.
  • 00:44:40
    We'd like to balance the numbers of sub-problems by polynomial.
  • 00:44:43
    So these are three natural ways to get polynomial bounds.
  • 00:44:47
    Now, prefixes and suffixes are obviously better
  • 00:44:50
    because there's fewer of them, linear instead of quadratic.
  • 00:44:53
    And usually almost every problem you encounter,
  • 00:44:56
    prefixes and suffixes are equally good.
  • 00:44:58
    It doesn't really matter which one you choose.
  • 00:45:00
    So maybe you'd like to think of--
  • 00:45:03
    well, we'll get to--
  • 00:45:04
  • 00:45:06
    just choose whichever is more comfortable for you.
  • 00:45:11
    But sometimes it's not enough.
  • 00:45:12
    And we'll have to go to substrings.
  • 00:45:13
    That won't be for another lecture or two.
  • 00:45:16
    Today I claim that prefixes or suffixes
  • 00:45:19
    are enough to solve the bowling problem.
  • 00:45:24
    So what we're going to do is think about--
  • 00:45:26
    I prefer suffixes usually, because I
  • 00:45:29
    like to work from left to right, from the beginning to the end.
  • 00:45:32
    So we're going to think of a suffix of the bowling pins.
  • 00:45:37
    And so what is the sub-problem on a suffix?
  • 00:45:39
    Well, a natural version is just to solve the original problem,
  • 00:45:43
    bowling.
  • 00:45:44
    How do I maximize my score if all I were given
  • 00:45:46
    were these pins?
  • 00:45:47
    Suppose the pins to the left of i didn't exist.
  • 00:45:50
    How would I maximize my score on the remaining pins?
  • 00:45:53
    Or for this suffix, given these four pins, what would I do?
  • 00:45:56
    And there's some weird sub problems here.
  • 00:45:58
    If I just gave you the last pin, what would you do?
  • 00:46:00
    Nothing.
  • 00:46:01
    That's clearly different from what I would do globally here.
  • 00:46:04
    But I claim if I can solve all suffixes
  • 00:46:06
    I can solve my original problem, because one of the suffixes
  • 00:46:09
    is the whole sequence.
  • 00:46:11
  • 00:46:14
    So let's do it.
  • 00:46:15
  • 00:46:18
    Sort by for bowling.
  • 00:46:22
  • 00:46:26
    So here is our dynamic program.
  • 00:46:29
    The sub-problems are suffixes.
  • 00:46:34
    So I'll write b of i is the maximum score we could get
  • 00:46:42
    possible with our starting--
  • 00:46:50
    if we started a game with pins i, i plus 1, up to n minus 1,
  • 00:47:00
    which is a suffix of the pins.
  • 00:47:02
    Very important whenever you write a dynamic program
  • 00:47:04
    to define what your sub-problems are.
  • 00:47:06
    Don't just say how to compute them,
  • 00:47:08
    but first say what is the goal of the sub problem.
  • 00:47:10
    This is a common mistake to forget to state
  • 00:47:13
    what you're trying to do.
  • 00:47:15
    So now I have defined b of i.
  • 00:47:18
    Now, what is the original thing I'm trying to solve?
  • 00:47:23
    You also put in SRTBOT--
  • 00:47:24
    you could put the O earlier, then it actually spells sort.
  • 00:47:28
    So why don't I do that for fun.
  • 00:47:30
    The original problem we're trying to solve is b of 0,
  • 00:47:35
    because that is all of the pins.
  • 00:47:36
    The suffix starting at 0 is everything.
  • 00:47:39
    So if we can solve that, we're done.
  • 00:47:42
    Next is r for relate.
  • 00:47:46
    This is the test of, did I get the sub-problems right,
  • 00:47:49
    is whether I can write a recurrence relation.
  • 00:47:53
    So let's try to do it.
  • 00:47:54
    We want to compute b of i.
  • 00:47:58
    So we have pin i here and then the remaining pins.
  • 00:48:04
  • 00:48:07
    And the big idea here is to just think about--
  • 00:48:10
    the nice thing about suffixes is if I take off
  • 00:48:13
    something from the beginning, I still have a suffix.
  • 00:48:15
    Remember, my goal is to take this sub-problem, which
  • 00:48:18
    is suffix starting at i, and reduce it
  • 00:48:20
    to a smaller sub problem, which means a smaller suffix.
  • 00:48:23
    So I'd like to clip off one or two items here.
  • 00:48:28
    And then the remaining problem will be one of my sub problems.
  • 00:48:33
    I'll be able to recursively call b of something smaller than i--
  • 00:48:37
    or sorry, b of something larger than i
  • 00:48:39
    will be a smaller subsequence because we're starting later.
  • 00:48:43
    OK, so what could I do?
  • 00:48:44
    Well, the idea is to just look at pin i
  • 00:48:47
    and think, well, what could I do to pin i?
  • 00:48:50
    I could not hit it ever with a ball.
  • 00:48:52
    I could skip it.
  • 00:48:53
    That's one option.
  • 00:48:54
    What would be my score then?
  • 00:48:57
    Well, if I skip pin i, that leaves the remaining pins,
  • 00:49:02
    which is just a smaller suffix.
  • 00:49:04
    So that is b of i plus 1.
  • 00:49:08
    I'm going to write a max out here
  • 00:49:10
    because I'd like to maximize my score.
  • 00:49:12
    And one of the options is, forget about pin i.
  • 00:49:14
    Just solve the rest.
  • 00:49:16
    Another option is I throw a ball.
  • 00:49:18
    And I exactly hit pin i.
  • 00:49:21
    That's one thing I could do.
  • 00:49:22
    And it would leave exactly the same remainder.
  • 00:49:26
    So another option is b of i plus 1 plus vi.
  • 00:49:34
    Why would I prefer this over this?
  • 00:49:36
    Well, if vi is negative, I'd prefer this.
  • 00:49:39
    But if vi is positive, I'd actually prefer this over that.
  • 00:49:42
    So you can figure out which is better, just locally.
  • 00:49:47
    But then there's another thing I can do,
  • 00:49:49
    which is maybe I hit this pin in a pair with some other pin.
  • 00:49:54
    Now, there's no pin to the left of this one.
  • 00:49:56
    We're assuming we only have the suffix.
  • 00:49:58
    And so the only other thing I can do is throw a ball
  • 00:50:01
    and hit i together with i plus 1.
  • 00:50:03
    And then I get the product.
  • 00:50:05
    Now, what pins remain?
  • 00:50:07
    i plus 2 on.
  • 00:50:08
    Still a suffix.
  • 00:50:09
    So if I remove one or two items, of course,
  • 00:50:12
    I still get a suffix--
  • 00:50:13
    in this case, b of i plus 2--
  • 00:50:15
    and then the number of points that I add on
  • 00:50:18
    are vi times vi plus 1.
  • 00:50:21
    So this is a max of three things.
  • 00:50:26
    So how long does it take me to compute it?
  • 00:50:28
    I claim constant time.
  • 00:50:30
    If I don't count the time it takes
  • 00:50:31
    to compute these other sub problems, which
  • 00:50:33
    are smaller because they are smaller suffixes further
  • 00:50:37
    to the right, then I'm doing a couple
  • 00:50:40
    of additions-- product, max.
  • 00:50:42
    These are all nice numbers and I'll
  • 00:50:44
    assume that they live in the w-bit word,
  • 00:50:48
    because we're only doing constant sized products.
  • 00:50:50
    That's good.
  • 00:50:51
    So this takes constant, constant non-recursive work.
  • 00:50:56
    How many sub problems are?
  • 00:50:57
    Well, it's suffixes, so it's a linear number of sub problems.
  • 00:51:01
    And so the time I'm going to end up
  • 00:51:03
    needing is number of sub problems, n,
  • 00:51:07
    times the non-recursive work I do per sub problem, which
  • 00:51:11
    is constant.
  • 00:51:12
    And so this is linear time.
  • 00:51:16
    Great.
  • 00:51:16
    And I didn't finish SRTBOT, so there's
  • 00:51:19
    another t, which is to make sure that there
  • 00:51:21
    is a topological order and that is in decreasing i order.
  • 00:51:27
  • 00:51:30
    Or I might write that as a for loop--
  • 00:51:33
    for i equals n, n minus 1.
  • 00:51:37
    This is the order that I would compute my problems
  • 00:51:41
    because the suffix starting at n is the empty suffix.
  • 00:51:44
    The suffix starting at 0, that's the one I actually
  • 00:51:46
    want to compute.
  • 00:51:47
    That's the final suffix I should be computing.
  • 00:51:50
    And then we have a b for base case,
  • 00:51:53
    which is that first case, b of n equals 0,
  • 00:52:00
    because there's no pins.
  • 00:52:02
    So I don't get any points.
  • 00:52:04
    Sad.
  • 00:52:04
  • 00:52:07
    OK, so this is it.
  • 00:52:09
    We just take these components, plug them
  • 00:52:12
    into this recursive, memoized algorithm,
  • 00:52:14
    and we have a linear time algorithm.
  • 00:52:17
    I want to briefly mention a different way
  • 00:52:18
    you could plug together those pieces, which is called bottom
  • 00:52:21
    up dp, which is--
  • 00:52:27
    let's do it for this example.
  • 00:52:29
    So if I have--
  • 00:52:32
    let's see.
  • 00:52:34
    Let me start with the base case, b of n equals 0.
  • 00:52:39
    But now it's an assignment.
  • 00:52:41
    And I'm going to do for loop from the topological order
  • 00:52:44
    for i equals n, n minus 1 to 0.
  • 00:52:49
    Now I'm going to do the relation, b of i equals
  • 00:52:53
    max of b of i plus 1 and b of i plus 1 plus bi
  • 00:53:02
    and b of i plus 2 plus di vi plus 1.
  • 00:53:08
    Technically this only works if i is strictly less than n
  • 00:53:11
    minus 1.
  • 00:53:12
    So I should have an if i is less than minus 1 for that last part
  • 00:53:17
    because I can only do--
  • 00:53:18
    I can only hit two pins if there's at least two pins left.
  • 00:53:22
    And then return b of 0.
  • 00:53:27
    So what I just did is a transformation
  • 00:53:29
    from this SRTBOT template into a non-recursive algorithm, a
  • 00:53:35
    for loop algorithm, where I wrote my base case first.
  • 00:53:39
    Then I did my topological order.
  • 00:53:43
    Then I did my relation.
  • 00:53:46
    Then at the end, I did my--
  • 00:53:48
    not base case.
  • 00:53:49
    The original problem.
  • 00:53:50
  • 00:53:53
    And provided you can write your topological order
  • 00:53:56
    as some for loops.
  • 00:53:57
    This is actually a great way to write down a dp as code.
  • 00:54:00
    If I were going to implement this algorithm,
  • 00:54:02
    I would write it this way, because this is super fast.
  • 00:54:04
    No recursive calls.
  • 00:54:05
    Just one for loop.
  • 00:54:06
    In fact, this is almost a trivial algorithm.
  • 00:54:08
    It's amazing that this solves the bowling problem.
  • 00:54:12
    It's in some sense considering every possible strategy I could
  • 00:54:16
    for bowling these pins.
  • 00:54:19
    What we're using is what we like to call local brute force,
  • 00:54:22
    where when we think about pin i, we
  • 00:54:25
    look at all of the possible things I could do to pin i,
  • 00:54:28
    here there's really only three options of what I could do.
  • 00:54:31
    Now, normally, if I tried all the options for pin i
  • 00:54:34
    and then all the options for i plus 1 and i plus 2 and so on,
  • 00:54:37
    that would be exponential.
  • 00:54:38
    It'd be 3 times 3 times 3.
  • 00:54:40
    That's bad, but because I can reuse these sub problems,
  • 00:54:44
    it turns out to only be linear time.
  • 00:54:47
    It's almost like magic.
  • 00:54:49
    dp-- dp is essentially an idea of using local brute force.
  • 00:54:56
  • 00:55:02
    And by defining a small number of sub-problems up front--
  • 00:55:06
    and as long as I stay within those sub problems,
  • 00:55:09
    as long as I'm always recursing into this polynomial space,
  • 00:55:12
    I end up only doing polynomial work,
  • 00:55:15
    even though I'm in some sense exploring
  • 00:55:17
    exponentially many options.
  • 00:55:19
    And it is because what I do to this pin
  • 00:55:22
    doesn't depend too much to what I do to a pin much later.
  • 00:55:26
    There's a lot of intuition going on here for what--
  • 00:55:30
    when DP works.
  • 00:55:31
    But we're going to see a lot more examples of that
  • 00:55:33
    coming up.
  • 00:55:36
    And I just want to mention the intuition for how
  • 00:55:38
    to write a recurrence like this is
  • 00:55:40
    to think about-- in the case of suffixes,
  • 00:55:42
    you always want to think about the first item, or maybe
  • 00:55:44
    the first couple of items.
  • 00:55:46
    The case of prefixes, you always think about the last item.
  • 00:55:49
    And for substrings, it could be any item-- maybe in the middle.
  • 00:55:52
    If I remove an item from the middle of a substring,
  • 00:55:54
    I get two substrings, so I can recurse.
  • 00:55:57
    Here or in general, what we want to do
  • 00:56:00
    is identify some feature of the solution
  • 00:56:03
    that if we knew that feature we would be done.
  • 00:56:06
    We would reduce to a smaller sub problem.
  • 00:56:09
    In this case, we just say, well, what
  • 00:56:11
    are the possible things I could do to the first pin?
  • 00:56:15
    There are three options.
  • 00:56:16
    If I knew which option it was, I would be done.
  • 00:56:18
    I could recurse and do my addition.
  • 00:56:21
    Now, I don't know which thing I want to do.
  • 00:56:23
    So I just try them all and take the max.
  • 00:56:26
    And if you're maximizing, you take the max.
  • 00:56:28
    If you're minimizing, you take the min.
  • 00:56:29
    Sometimes you take an or or an and.
  • 00:56:32
    There might be some combination function.
  • 00:56:34
    For optimization problems where you're
  • 00:56:36
    trying to maximize or minimize something,
  • 00:56:37
    like shortest paths you're trying to minimize,
  • 00:56:39
    we put them in here.
  • 00:56:40
    So usually it's min or max.
  • 00:56:42
    And this is extremely powerful.
  • 00:56:46
    All you need to do--
  • 00:56:47
    the hard part is this inspired design part
  • 00:56:50
    where you say, what do I need to know that would
  • 00:56:54
    let me solve my problem?
  • 00:56:56
    And if you can identify that and the number of choices
  • 00:56:58
    for what you need to know is polynomial,
  • 00:57:01
    then you will be able to get a polynomial dynamic program.
  • 00:57:05
    That's the intuition.
  • 00:57:06
    You'll see a lot more examples in the next three lectures.
  • 00:57:10
Tags
  • dynamic programming
  • SRTBOT
  • memoization
  • algorithm design
  • Fibonacci
  • shortest paths
  • recursive algorithms
  • problem-solving
  • sub-problems
  • bowling game