1. Introduction to Dynamic Programming
Dynamic programming is a powerful technique to solve computational problems, which have a recursive substructure and recurring subproblems. The idea is to solve these recursive subcases and store these solutions in a lookup table. When a solved recursive subcase is encountered, the existing solution is accessed using only a constant number of steps. A solution to the initial instance is constructed from the solutions to the sub-cases, typically in a bottom-up manner. Frequently, the computational problems of interest are optimization problems. Common examples include the Shortest Path Problem, the Rod-Cutting Problem, and the Game of Nim. We provide a practical exposition, introducing some examples amenable to the dynamic programming technique. The goal of this tutorial is that the readers can successfully apply the dynamic programming technique. To that end, this tutorial is not a complete treatment of the subject. In particular, issues of computational complexity are largely not discussed here. Readers interested in more advanced expositions should direct their attention to common algorithm analysis texts, such as Sedgewick and Wayne, or CLRS.
1.1 The Game of Nim
We consider the following two-person game, in which players alternate turns. The game begins with a pile of (identical) stones. During a player’s turn, they may remove either
or
stones from the pile. If a player cannot make a move, that player loses. This game is denoted as a
-Nim game, in light of the allowed moves of removing 1, 2, or 3 stones. We define the game of Nim more formally.
Definition 1 (Nim). Let be distinct, and let
. The game
-Nim is a two player game, which is initialized with a pile of
stones. Players alternate removing stones from the pile, where the number of stones each player can remove on their turn lies in the set
. A player loses if they cannot make a move on a given turn.
Remark: In -Nim, a player loses if there are no stones left. However, in
-Nim, a player loses if there are fewer than 2 stones left on the pile.
We restrict attention to -Nim, with the goal of illustrating the dynamic programming technique to determine for which values of
Player 1 will win. Here, we assume both agents play optimally; that is, both players seek to win the game and are able to determine the best move to achieve their goal. We denote
to indicate a loss, and
to denote a win.
- We begin by initializing our lookup table
to store whether or not Player 1 will win, given a pile with
stones. Clearly, if
, Player 1 loses. So
. Similarly, if
, then Player 1 can take all the stones and win in one turn. So
.
- Now suppose there are
stones. No matter how many stones Player 1 takes (whether it be 1, 2, or 3 stones), Player 2 takes the remaining stones. So Player 1 always loses. Thus, we set
.
- Suppose there are
stones. Suppose Player 1 selects
stones. Now Player 2 is the first player in a smaller instance of
-Nim with
stones. Here, we begin to see the power of dynamic programming in constructing strategies. We have already computed whether Player 2 will win in the smaller instance of Nim with
stones, and so we can just look up the result in
. Observe that
if and only if
(i.e.,
). So for
, Player 1 starts by taking a single stone. Then Player 2 will lose. So
.
- Using similar reasoning as in part (c), we have that
.
- Now what happens if there are
stones? Suppose that Player 1 takes
stones. So Player 2 is the first player in a smaller instance of
-Nim with
stones. For each
,
. So Player 2 will always win. Thus,
.
While we can continue building the lookup table, it may be more insightful to look at the entries already present. Observe that and
are the only entries with
. This leads to the following observation.
Proposition 1.1. In -Nim with
stones in the pile, Player 1 has a winning strategy if and only if
is not a multiple of 4.
Proof: The proof is by strong induction on . We have the following base cases:
- Case: Suppose
. Player 1 has no available moves, and so Player 1 loses.
- Case: Suppose
. Player 1 takes all available stones. So Player 2 has no moves and loses. Thus, Player 1 wins.
Now fix , and suppose that the proposition holds for all
. We prove true for the
case. have the following cases:
- Case: Suppose that
is not a multiple of 4. By the Division Algorithm, we may write
for some
. We show that it is a winning strategy for Player 1 to take
stones on their first turn. After Player 1 takes
stones, Player 2 takes their turn with
stones remaining. Observe that Player 2 is the first player in a smaller instance of
-Nim with
stones. By the Inductive Hypothesis, Player 2 has no winning strategy, as
is a multiple of 4. So Player 1 has a winning strategy, as claimed.
- Case: Suppose that
is a multiple of 4. For any
,
is not a multiple of
. Suppose Player 1 removes
stones from the pile. Then Player 2 is the first player in a smaller instance of
-Nim with
stones. As
and
is not a multiple of
, we have by the inductive hypothesis that Player 2 has a winning strategy. Thus, Player 1 does not have a winning strategy, as claimed.
The result follows by induction. QED.
Remark: When working with instances of Nim, it is helpful to employ dynamic programming with the goal of determining the period of the game, or the length of the pattern of wins and losses that repeat within the lookup table. Once this pattern is ascertained, we may appeal to the pattern to decide in constant time who wins the game. More exposition and generalizations of Nim are discussed in Combinatorial Game Theory, and we direct interested readers to look there for more in-depth exposition on Nim.
1.2 Rod-Cutting Problem
In this section, we examine the Rod-Cutting Problem. Let us consider a motivating example. Suppose we have a rod of length , which can be cut into smaller pieces of lengths
or
These smaller rods can then further be cut into smaller pieces. Now suppose that we can sell rods of length 1 for $1, which we denote
. Similarly, suppose that the prices for rods of length
are given by
and
respectively. We make two key assumptions: we will sell all the smaller rods, regardless of the cuts; and that each cut is free. Under these assumptions, how should the rod be cut to maximize the profit? We note the following cuts and the corresponding profits.
- If the rod is cut into five pieces of length
, we stand to make
.
- If the rod is cut into one piece of length
and one piece of length
, we stand to make
.
- If the rod is cut into two pieces of length
and one piece of length
, we stand to make
.
Out of the above options, cutting the rod into one piece of length 2 and one rod of length 3 is the most profitable. Of course, there are other possible cuts not listed above, such as cutting the rod into one piece of length 1 and one piece of length 4. The goal is to determine the most profitable cut. The Rod-Cutting Problem is formalized as follows.
Definition 2 (Rod-Cutting Problem).
- Instance: Let
be the length of the rod, and let
be non-negative real numbers. Here,
is the price of a length
rod.
- Solution: The maximum revenue, which we denote
, obtained by cutting the rod into smaller pieces of integer lengths and selling the smaller rods.
Intuitively, the maximum revenue is determined by examining the revenues for the subdivisions and taking the largest. Mathematically, this amounts to the following expression:
We begin by working through an example of utilizing dynamic programming to determine the maximum profit.
Example 1. Suppose we have a rod of length 5, with prices . We proceed as follows.
- Initialize a lookup table
. Now for a rod of length 1, there is only one price:
. So we set
.
- Now consider a rod of length 2. There are two options: either don’t cut the rod, or cut the rod into two smaller pieces of length 1. Here,
represents the case in which no cuts to a rod of length 2. Now suppose instead we cut the rod up into two smaller pieces each of length 1. We know that the maximum profit for a rod of length 1 is
. So the profit of cutting the rod into two smaller pieces each of length 1 is
. Now
, so we set
.
- Now consider a rod of length 3. Here, we have more options: we can leave the rod untouched, we can divide the rod into smaller pieces of length 1 or length 2; or we can divide the rod into three pieces of length 1. If we do not divide the rod into smaller pieces, the profit is
. Now suppose we divide the rod up into smaller pieces of length 2 and length 1. We may now keep this configuration, or further divide the rod of length 2 into two rods each of length 1, as discussed in the previous bullet point. Rather than re-solving this problem, we can simply look up the maximum profit for a rod of length 2 in the lookup table. Recall that
and
. So the profit from cutting the rod into smaller pieces of length 2 and length 1 is
. Now
, so we set
.
- Now consider a rod of length 4. We have the following options for the first cut: leave the rod uncut, in which we stand to make profit
; cut the rod into smaller pieces of length 1 and length 3; or cut the rod into two smaller rods, each of length 2. Consider the case in which we cut the rod up into smaller pieces of length 1 and length 3. The natural, though inefficient, approach here is to consider all the ways in which we could cut up the rod of length 3. It turns out that we don’t need to do this, as the maximum revenue attainable from a rod of length 3 was found in the previous bullet point. This is the power of dynamic programming: once a solution to a smaller problem is found, we simply look it up rather than re-solving the smaller problem. Similarly, we can look up the maximum profit for a rod of length 2. So given our cases, we have the following possible profits:
- The uncut rod of length 4 will result in profit
.
- The rod cut into pieces of length 3 and length 1 will result in profit
.
- The rod cut into two pieces, each of length 2, will result in profit
.
So
.
- The uncut rod of length 4 will result in profit
- Finally, consider our original rod of length 5. We have the following possible initial cuts:
- We can leave the rod uncut, in which case we will make profit
.
- We can cut the rod into one piece of length
and one piece of length 1. The maximum revenue attainable by cutting up a rod of length 4 was determined already. So we can simply look up this solution in
. Thus, the profit in this case is
.
- We can cut up the rod into one piece of length 3 and one piece of length 2. By similar argument as above, we may simply look up the maximum revenues attainable from a rod of length 3 and a rod of length 2. So our profit is
.
So
. Thus, we set
.
- We can leave the rod uncut, in which case we will make profit
We conclude that we stand to make from a rod of length 5.
While the expression
may not seem insightful, it in fact provides an algorithm to compute . Example 1 provides a tangible example of this algorithm. The goal now is to generalize the algorithm from Example 1 to work for any rod of positive integer length any list of prices. We proceed as follows.
- Initialize the lookup table
, and set
.
- We set
. Here,
represents the case in which no cuts to a rod of length 2, and
represents the case in which a rod of length 2 is cut into two rods each of length 1. We note that
.
- We set
. Now
, and
. We have already solved the rod cutting problem for a length 2 rod, so we simply look up
in the table
rather than re-solving the problem.
. As we have already computed
, we may look up their respective values in
rather than re-computing these values.
Continuing in this manner, we compute , which is the value in
after the algorithm terminates.
Remark: This algorithm only provides the maximum revenue. It does not tell us how to achieve that result. As an exercise, modify the algorithm to produce an optimal set of rod cuts.
1.3 Longest Common Subsequence Problem
Solutions to both the Rod-Cutting Problem and Nim utilized dynamic programming techniques, where the lookup table was one-dimensional. In this section, we introduce the Longest Common Subsequence Problem, which is also amenable to the dynamic programming technique. However, unlike the Rod-Cutting Problem and Nim, the lookup table for the Longest Common Subsequence Problem is a two-dimensional table rather than a one-dimensional array. The purpose of this section is to illustrate the usage of multidimensional lookup tables in dynamic programming problems. To this end, the Longest Common Subsequence Problem serves as a tangible example. We begin by formalizing the Longest Common Subsequence Problem.
Definition 3 (Subsequence). Let be a finite set, which we refer to as an alphabet. Let
. We say that
is a subsequence of
if there exists a strictly increasing sequence of indices
such that
for all
.
Example 2. Let , and let
. Consider the sequence of indices
. So
and
. Thus,
is a subsequence of
.
Definition 4 (Common Subsequence).
Let be an alphabet. Let
be sequences. We say that
is a common subsequence of
and
if:
is a subsequence of
, and
is a subsequence of
. Note that
does not have to appear as a subsequence in the same position in both
and
.
Example 3. Let and
. The sequence
is a subsequence of both
and
. Here,
appears in
at the indices
, and
appears in
at the indices
.
Definition 5 (Longest Common Subsequence Problem (LCS)).
- Instance: Let
be an alphabet, and let
be sequences.
- Solution: A sequence
that is common to both
and
; and for any other common subsequence
of
and
,
.
The naive approach to solving LCS is enumerating all the possible subsequences of and
, and recording the longest. Without loss of generality, suppose that
. So there are
possible index sequences to check, which correspond bijectively to subsequences of
. So for large sequences, the brute force and ignorance solution is not a practical solution. The dynamic programming approach provides a linear time algorithm instead.
Dynamic programming works best when optimal solutions to subproblems can be used to construct an optimal solution to the original instance. We first show that LCS exhibits this property.
Theorem 1.1. Let be an alphabet, and let
be sequences. Let
be a longest common subsequence of
and
. The following hold:
- If
, then
and
is a longest common subsequence of
and
.
- If
and
, then
is a longest common subsequence of
and
. Similarly, if
and
, then
is a longest common subsequence of
and
.
Proof:
- Let
be a common subsequence of
and
whose last digit does not correspond to the last instance of the character
in
and
. Then
can be augmented by appending the character
. So every longest common subsequence of
and
has last character
.We now show that
is a longest common subsequence of
and
. Observe that
is a common subsequence of
and
. Suppose to the contrary that there exists a longest common subsequence
of
and
, with
. Then
can be augmented with
to obtain a common subsequence of
and
. This contradicts the assumption that any longest common subsequence of
and
has length
. So
is a longest common subsequence of
and
.
- Suppose that
. Now suppose that
. We show that
is a longest common subsequence of
and
, by contradiction. Let
be a longest common subsequence of
and
of length
. Clearly,
is a common subsequence of
and
. Now
, contradicting the assumption that
was a longest common subsequence of
and
. So
is a longest common subsequence of
and
. Interchanging the roles of
and
, we obtain that: if
and
, then
is a longest common subsequence of
and
.
Theorem 1.1 provides the insights necessary for designing a dynamic programming algorithm to solve LCS. Let be sequences. If
, we record the last character and examine the smaller LCS instance with
and
. If
. Otherwise, we need to find the longest common subsequences of
and
; and
and
. These observations yield a natural recurrence to compute the length of the longest common subsequence for a pair of strings:
Using the recurrence as a template, we design an explicit dynamic programming algorithm. We proceed as follows.
- Let
be our input sequences. We initialize a lookup table
to be a two-dimensional array, where each cell stores:
- A natural number corresponding to the length of a longest common subsequence; and
- A pointer to another cell in the lookup table, which corresponds to the optimal subproblem as specified in Theorem 1.1.
Now recall that if either of the input sequences have length
, the length of the longest common subsequence is
. Therefore, we set
and
for all
and all
. While our original input sequences may not have length
, sequences we encounter in subproblems may indeed have length
.
- We now proceed to fill in the remaining cells in a bottom up manner, row-by-row. Each row is filled left-to-right. The cells
are filled as follows.
- Case 1: Suppose
. By Theorem 1.1, any longest common subsequence
of
and
ends with
. Furthermore,
is a longest common subsequence of
and
. So we take the following actions:
- Set
; and
- Set
.
- Set
- Case 2: Suppose
. Theorem 1.1 tells us that we need to consider the two subproblems, whose solutions (or at least, their optimal lengths) are stored in:
and
, respectively. If
, we set:
-
-
.
-
-
-
.
Otherwise, we set:
- Case 1: Suppose
- We begin by initializing a
lookup table
, and filling the first row and column with
‘s. So we have:
- We now fill Row 1.
- Consider
. Observe that
and
are different. So
is the maximum of
and
. Thus,
. By Case 2 of the algorithm,
points to
.
- Consider
. Observe that
. So
, and \newline
points to
.
- Consider
. Observe that
and
are different. So
is the maximum of
and
. So
, and
points to
.
- Consider
. Observe that
and
are different. By similar argument as for
and
, we set
and
to point to
.
- Consider
. By similar argument as for
,
and
points to
.
The updated lookup table is as follows:
- Consider
- We next fill Rows 2-3, omitting the detailed explanation associated with filling Row 1. The completed lookup table is as follows.
- Finally, we construct a longest common subsequence of
and
from the lookup table. We start at
and follow the arrows, prepending the character at the given index every time we see
. So we have the sequence:
After which, we stop, as
does not reference any subproblems. So our longest common subsequence is
, which we identified at the start of this example.
In order to construct a longest common subsequence from the lookup table , we start at
and follow the pointers to the subproblem. Each time some
points to
as a subproblem, we prepend the character
to the front of the longest common subsequence. We stop once the currently visited cell has no pointer to a subproblem.
Example 4. Let and
. By inspection, it is easy to see that any longest common subsequence of
and
has length
. In particular,
, and
are all longest common subsequences of
and
. We work through the dynamic programming algorithm to explicitly find a longest common subsequence.










