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