WEBVTT

00:00:04.600 --> 00:00:10.150
There are many ways to complete a task---even
a seemingly simple one like eating cereal.

00:00:10.150 --> 00:00:14.549
When programming a computer to complete a
task or solve a problem, repetitive techniques

00:00:14.549 --> 00:00:19.650
like iteration and recursion are extremely
useful. In this video, we will look at these

00:00:19.650 --> 00:00:21.870
problem-solving techniques.

00:00:21.870 --> 00:00:27.520
This video is part of the Problem Solving
video series. Problem-solving skills, in combination

00:00:27.520 --> 00:00:33.280
with an understanding of the natural and human-made
world, are critical to the design and optimization

00:00:33.280 --> 00:00:34.949
of systems and processes.

00:00:34.949 --> 00:00:41.260
Hi, my name is Niaja Farve. I am a doctoral
student in Electrical Engineering and Computer

00:00:41.260 --> 00:00:43.679
Science at MIT.

00:00:43.679 --> 00:00:48.019
Before watching this video, you should be
familiar with introductory programming and

00:00:48.019 --> 00:00:50.469
simple data structures.

00:00:50.469 --> 00:00:55.979
After watching this video, you will be able
to: Divide a programming problem into simpler,

00:00:55.979 --> 00:01:02.979
analogous pieces. And, solve the problem by
combining solutions to the simpler pieces.

00:01:07.440 --> 00:01:12.850
In computer science, we often want to solve
complex problems. However, computers deal

00:01:12.850 --> 00:01:19.380
best with performing easy tasks over and over
again. We utilize the computer's ability by

00:01:19.380 --> 00:01:24.908
implementing repetitive techniques to incrementally
solve our complex problems.

00:01:24.908 --> 00:01:30.600
Though eating a bowl of cereal is a fairly
simple task that most of us can complete automatically,

00:01:30.600 --> 00:01:35.880
we need to think carefully about how to program
a computer to do the same. Let's take a closer

00:01:35.880 --> 00:01:42.100
look at the problem and identify the fundamental
steps used to frame cereal eating for repetitive

00:01:42.100 --> 00:01:43.100
computation.

00:01:43.100 --> 00:01:47.658
In this example, let's suppose the computer
understands the basic operation of eating

00:01:47.658 --> 00:01:53.929
a single bite of cereal, but does not understand
how to eat an entire bowl of cereal. So how

00:01:53.929 --> 00:01:59.479
do we "teach" the computer to eat a bowl of
any size greater than one bite? Pause the

00:01:59.479 --> 00:02:06.479
video here and think about it.

00:02:07.770 --> 00:02:11.930
The first step when approaching a complex
programming problem is to break the problem

00:02:11.930 --> 00:02:17.750
up into analogous, but simpler pieces that
we can tell the computer how to directly solve.

00:02:17.750 --> 00:02:22.350
So what is a simpler version of eating a whole
bowl of cereal?

00:02:22.350 --> 00:02:26.480
One possibility is eating a smaller amount
of cereal.

00:02:26.480 --> 00:02:31.300
We already mentioned that a small, non-zero
amount of cereal the computer can handle eating

00:02:31.300 --> 00:02:34.220
is a single bite.

00:02:34.220 --> 00:02:39.430
Going one step up, eating two bites-worth
of cereal is equivalent to eating a single

00:02:39.430 --> 00:02:46.210
bite twice. The next step when approaching
a complex problem is to deduce a pattern.

00:02:46.210 --> 00:02:51.480
That is, how does a generally larger problem
look in comparison to the simpler version?

00:02:51.480 --> 00:02:56.260
Here, we notice that eating any amount of
cereal is equivalent to the sum of eating

00:02:56.260 --> 00:02:59.230
multiple bites-worth of cereal.

00:02:59.230 --> 00:03:04.740
Now that we've broken up our problem and understand
how the pieces will fit back together, we

00:03:04.740 --> 00:03:09.180
can put our solution into a generalized code
framework:

00:03:09.180 --> 00:03:14.120
Start with telling the computer the procedure
for solving the simplest problem. Then, repeat

00:03:14.120 --> 00:03:19.130
this procedure on subsequent pieces until
the desired endpoint is reached:

00:03:19.130 --> 00:03:26.130
If the bowl contains cereal, take one bite
of cereal. Repeat until there is no more cereal.

00:03:31.540 --> 00:03:36.250
You may recognize this type of solution as
an iterative approach.

00:03:36.250 --> 00:03:39.810
Or we could also take the following alternative
approach:

00:03:39.810 --> 00:03:44.500
Start with telling the computer how to solve
the simplest problem. Then, break the problem

00:03:44.500 --> 00:03:49.320
into simpler and simpler pieces until we reach
the version we've already told the computer

00:03:49.320 --> 00:03:54.840
how to solve.
Does the bowl contain 1 bite of cereal? If

00:03:54.840 --> 00:04:01.030
so, take the bite. If not, divide it up into
a bowl containing one bite and a bowl containing

00:04:01.030 --> 00:04:06.090
the remainder. Repeat this procedure on the
resulting bowls.

00:04:06.090 --> 00:04:11.040
In this case, we end, up with a series of
bowls containing one bite, which the computer

00:04:11.040 --> 00:04:13.200
knows how to eat.

00:04:13.200 --> 00:04:17.880
Breaking up a problem into progressively simpler,
but analogous pieces in this way is known

00:04:17.880 --> 00:04:23.350
as a recursive approach. Because the solution
to the most complex problem depends on solutions

00:04:23.350 --> 00:04:28.930
to the simpler pieces, recursion creates a
queue of jobs waiting to be completed.

00:04:28.930 --> 00:04:35.260
In contrast, when using iteration, there is
no such dependency from one instance of the

00:04:35.260 --> 00:04:37.260
problem to the next.

00:04:37.260 --> 00:04:43.030
So even though the computer ends up consuming
n bites of cereal in both cases, the iterative

00:04:43.030 --> 00:04:48.150
and recursive approaches arrive at this answer
in very different ways.

00:04:48.150 --> 00:04:53.690
There are many different ways to successfully
eat a bowl of cereal or solve any given programming

00:04:53.690 --> 00:05:00.380
problem. The key is to 1. Break the problem
into analogous pieces that the computer can

00:05:00.380 --> 00:05:05.790
solve, and 2. Combine the solutions to the
pieces into a solution for the more complex

00:05:05.790 --> 00:05:11.620
problem.

00:05:11.620 --> 00:05:17.470
In our previous cereal-eating example, breaking
down the problem into simpler pieces was fairly

00:05:17.470 --> 00:05:21.260
straightforward. Now, let's look at a slightly
more complex problem.

00:05:21.260 --> 00:05:27.250
We would like to write a function, downup,
that takes an input string and prints out

00:05:27.250 --> 00:05:32.260
progressively smaller and larger sub-strings
of the word as so.

00:05:32.260 --> 00:05:38.190
To help us work with strings, we have a helper
function, substring, which extracts a portion

00:05:38.190 --> 00:05:44.440
of a string, beginning from the first character
up though a specified end index.

00:05:44.440 --> 00:05:48.900
Following the framework we discussed earlier,
what is a simpler version of downup that we

00:05:48.900 --> 00:05:50.980
can easily handle?

00:05:50.980 --> 00:05:56.460
How about downup of a single letter string.
The desired output is achieved by simply printing

00:05:56.460 --> 00:05:58.500
the string.

00:05:58.500 --> 00:06:03.530
Moving one step up, we see that the desired
output of a two-letter string can be accomplished

00:06:03.530 --> 00:06:08.070
by printing the string, printing the string
shortened by one letter, and printing the

00:06:08.070 --> 00:06:11.120
full string a second time.

00:06:11.120 --> 00:06:16.650
And moving up one more step to a 3 letter
string...

00:06:16.650 --> 00:06:23.650
Are you starting to notice any patterns? Pause
the video to think of a possibility.

00:06:27.100 --> 00:06:31.370
Though there are many different patterns,
here is one you may have come up with: With

00:06:31.370 --> 00:06:36.740
every string, we are sandwiching the _solution_
to a string one character shorter between

00:06:36.740 --> 00:06:42.490
two printings of the full string. With this
mindset, we are poised for a recursive solution

00:06:42.490 --> 00:06:44.470
to this problem.

00:06:44.470 --> 00:06:49.680
Recall the general framework for a recursive
solution: Tell the computer how to solve the

00:06:49.680 --> 00:06:56.199
simplest problem. Then break the problem into
simpler pieces until we reach the simplest

00:06:56.199 --> 00:07:02.650
problem: If the string is a single letter,
print it. Otherwise, print the string, solve

00:07:02.650 --> 00:07:09.010
for downup of the string one character shorter,
and print the string again.

00:07:09.010 --> 00:07:15.180
Try now, if you haven't already done so, to
frame the problem in a more iterative manner.

00:07:15.180 --> 00:07:22.180
Pause the video to discuss.

00:07:23.770 --> 00:07:29.240
We can notice that we are repeatedly printing
substrings of the full string, with each step

00:07:29.240 --> 00:07:35.639
moving the end index from the original length
down to 1. This is followed by again printing

00:07:35.639 --> 00:07:41.840
substrings, but this time increasing the end
index back up to the original length.

00:07:41.840 --> 00:07:48.840
We can program this solution using two iterative
loops. Recall the general iterative code framework:

00:07:48.979 --> 00:07:54.110
Tell the computer the procedure for the simplest
problem. Repeat the procedure on subsequent

00:07:54.110 --> 00:07:57.460
pieces until the endpoint is reached.

00:07:57.460 --> 00:08:02.490
In our first loop, we print the substring,
decrease the index by one, and repeat the

00:08:02.490 --> 00:08:06.490
procedure until the index reaches one.

00:08:06.490 --> 00:08:12.130
In the second loop, we move in the opposite
direction. Print the substring. Increase the

00:08:12.130 --> 00:08:18.460
index by one and repeat the procedure until
the index is greater than the original length.

00:08:18.460 --> 00:08:24.040
Both approaches, while very different, are
completely valid! There is no one correct

00:08:24.040 --> 00:08:31.040
way to solve a problem. Some solutions may
even have both recursive and iterative elements.

00:08:36.059 --> 00:08:40.698
Now lets solve a third problem with an even
more complex pattern.

00:08:40.698 --> 00:08:45.589
In the famous Towers of Hanoi problem, the
goal is to transfer a stack of rings from

00:08:45.589 --> 00:08:52.589
pillar A to pillar C. We can only move a single
ring at a time, and can use pillar B as "extra"

00:08:54.050 --> 00:08:59.959
workspace. We cannot place a larger ring on
top of a smaller ring.

00:08:59.959 --> 00:09:06.369
Lets follow the framework and start by working
out the simplest problem: transferring 1 ring.

00:09:06.369 --> 00:09:09.119
Here we can simply move the ring from A to
C.

00:09:09.119 --> 00:09:16.119
Okay, now let's try to transfer a stack of
two rings. First we move the ring 1 to B,

00:09:17.550 --> 00:09:22.410
then ring 2 to C, then move ring 1 from B
to C.

00:09:22.410 --> 00:09:29.410
Now, let's try something a bit harder. Let's
try to transfer a stack of three rings. Ring

00:09:30.550 --> 00:09:37.550
1 moves to C, ring 2 goes to B, and ring 1
goes to B. Now ring 3, which was on the bottom,

00:09:40.889 --> 00:09:47.889
is free to move to C. Then, ring 1 goes to
A, ring 2 goes to C, then ring 1 finally goes

00:09:49.470 --> 00:09:50.939
to C.

00:09:50.939 --> 00:09:57.369
Notice that after we moved the bottom ring
to C, we essentially arrived at the same conformation

00:09:57.369 --> 00:10:03.959
as when trying to transfer 2 rings: We have
two stacked rings and an extra empty pillar.

00:10:03.959 --> 00:10:09.449
And because the largest ring is in the desired,
final position and does not impede the movements

00:10:09.449 --> 00:10:15.459
of any of the remaining smaller rings, we
can treat the pillar as being empty.

00:10:15.459 --> 00:10:22.249
This observation is crucial to the recursive
implementation of the towers of Hanoi solution.

00:10:22.249 --> 00:10:29.059
We transferred n-1 rings to the extra pillar,
moved the largest ring to the final position,

00:10:29.059 --> 00:10:33.399
then transferred the n-1 rings to the final
position.

00:10:33.399 --> 00:10:38.529
Can you frame a pseudocode solution to the
problem? Pause the video here to work out

00:10:38.529 --> 00:10:45.529
a possible solution.

00:10:46.589 --> 00:10:52.509
Recall again the general recursive framework:
Tell the computer how to solve the simplest

00:10:52.509 --> 00:10:59.259
problem: If we're transferring a single ring,
move it to the destination pillar.

00:10:59.259 --> 00:11:05.579
Then, break the problem up into progressively
simpler pieces:

00:11:05.579 --> 00:11:11.929
Transfer n-1 rings from the source to the
extra pillar, transfer ring n from the source

00:11:11.929 --> 00:11:18.929
to the destination, and transfer n-1 rings
from the extra pillar to the destination.

00:11:19.279 --> 00:11:24.399
Note that the source, destination, and extra
pillar designations change with each function

00:11:24.399 --> 00:11:25.569
call!

00:11:25.569 --> 00:11:31.139
It's always a good idea to check your code
with a test case. Pause the video here and

00:11:31.139 --> 00:11:37.990
check your code for the case of N equals 4.
You may also wish to check our code and compare

00:11:37.990 --> 00:11:44.990
the two solution methods.

00:11:46.529 --> 00:11:51.420
Now lets check our code and traverse through
the solution for transferring 4 rings.

00:11:51.420 --> 00:11:58.189
We're not transferring a single ring, so lets
transfer 3 rings from A to B.

00:11:58.189 --> 00:12:04.379
We're still not transferring one ring, so
lets transfer 2 rings from A to C.

00:12:04.379 --> 00:12:10.569
We're still not transferring one ring, so
lets transfer 1 ring from A to B. And we can

00:12:10.569 --> 00:12:14.179
finally move this single ring!

00:12:14.179 --> 00:12:21.179
Now we return to the previous call, and can
move ring 2 from A to C. Then we transfer

00:12:21.309 --> 00:12:28.309
our n-1 stack from B to C. This results in
transferring 2 rings from A to C!

00:12:32.749 --> 00:12:39.749
Returning one more step back, we move ring
3 to B. Now we transfer 2 rings from C to

00:12:40.839 --> 00:12:47.839
A.

00:12:48.379 --> 00:12:55.379
Finally, we've returned back to our original
function call, and we've completed transferring

00:13:00.220 --> 00:13:07.220
3 rings from A to B. So, we can move our largest
ring number 4 from A to C.

00:13:10.420 --> 00:13:16.600
Now we need a whole other set of recursive
function calls to transfer the 3 ring stack

00:13:16.600 --> 00:13:22.480
from B to C! Pause the video now to finish
checking the second half of our solution.

00:13:22.480 --> 00:13:29.480
Notice how quickly the number of function
calls grew! Good thing we have a computer

00:13:30.170 --> 00:13:34.269
that is very good at following repetitive
instructions!

00:13:34.269 --> 00:13:39.579
To Review In this video, we showed you how
recursion and iteration take advantage of

00:13:39.579 --> 00:13:43.550
a computer's ability to repeat simple tasks.

00:13:43.550 --> 00:13:48.889
To approach a complicated programming problem,
first solve some simpler versions and try

00:13:48.889 --> 00:13:54.470
to identify a pattern. Then, depending on
the type of pattern you found, fill in a recursive

00:13:54.470 --> 00:14:00.679
or iterative code framework. And remember,
iterative, recursive and even mixed solutions

00:14:00.679 --> 00:14:07.679
to a single problem may all be correct!