Data Structures and Algorithms

Dynamic Programming and Memoization

Advantage of a typical recursive algorithm is that you divide the domain into independent sub-problems. Some recursive problems, however, do not divide into independent subproblems. This is what dynamic programming is for.

You have to have:

  • A problem that can be broken down into smaller problems.
  • Overlap between subproblems.

The motivation:

  • Reduces the running time of a recursive function.
  • The previous small solutions should be in a table.

Memoization

The idea is that the if the same functions get called repeatedly with the same inputs, save these results and return them instead of re-computing. This is trading space for speed. This is known as memoization.

When you call a function, check the inputs and see if they have been seen before. If they have, just retrieve the result from memory rather than recomputing it. On exit, save the inputs and save the results. Often, this can be done with only minor code changes.

This technique is called memoization, which is useful for "top down" dynamic programming.

Example: Fibonacci Numbers

Recursive implementation of the Fibonacci numbers:

int fib(int n) {
    if (n < 1) return 0;
    if (n == 1) return 1;
    return fib(n - 1) + fib(n - 2);
}

This is bad. This has an exponential runtime.

If you know the basis (0 and 1), then you can use previously computed values like so:

int fib(int n) {
    int f[MAX_N];
    f[0] = 0;
    f[1] = 1;

    for (int k = 2; k <= i; k++) {
        f[k] = f[k - 1] + f[k - 2];
    }

    return f[i];
}

This way is a lot better. This has a linear runtime.

Nuances

Bottom-up dynamic programming can be used whenever top-down dynamic programming is used. Time or space requirements may become prohibitively large.

In a bottom up approach, you compute values from the base case up to the solution. This is "loosely non-adaptive", which means that it will compute all smaller cases, even if they're not needed.

In a top-down approach, you save values as they are needed. This method is generally preferred, because it is consistent with a recursive implementation, and is completely adaptive.

Problems

If the number of possible sub-problems is too high, then you're better off not trying to remember any values at all. This is an advanced technique for difficult problems. This is especially helpful when the solution domains are all dependent upon one another.