Chapter 13

Dynamic Programming

Turn repeated computation into a systematic sequence of reusable decisions. Learn to define states, derive recurrences, choose an evaluation order, and recover optimal solutions through Fibonacci, knapsack, matrix-chain multiplication, all-pairs shortest paths, rod cutting, and longest common subsequence. Use the diagrams and worked examples to connect each recurrence with the choices it represents.

Interactive chapterUse Step to inspect one state transition at a time, Play for the complete table construction, and Reset to start again. Select Full size on a diagram for a larger view.
Learning outcomes

By the end of this chapter, you will be able to model a DP state, justify its recurrence, analyze time and space, and reconstruct the decisions behind an optimal answer.

01 · Define the state02 · Identify dependencies03 · Evaluate once04 · Recover the solution

13.1Why dynamic programming?

Dynamic programmingSolve subproblems once, store their answers, and reuse those answers whenever the same subproblem reappears.
Overlapping subproblems

The recursion repeatedly asks for identical states, such as F(3) or KP(i,W).

Optimal substructure

An optimal solution can be constructed from optimal solutions of smaller states.

A DP solution is a dependency graph of states. The recurrence specifies the incoming dependencies; memoization or tabulation determines how the graph is evaluated. Correctness comes from showing that the recurrence covers every valid possibility and combines already-correct smaller answers.

When DP gives no benefitFactorial has no repeated subproblem in one evaluation, and merge-sort subproblems are disjoint rather than identical. DP is therefore not useful for a single n! or for ordinary merge sort.

13.2Fibonacci: recursion versus DP

def fibonacci(n):
    if n == 0: return 0
    if n == 1: return 1
    return fibonacci(n-1) + fibonacci(n-2)

Throughout this chapter, use zero-based indexing: F(0)=0, F(1)=1, and F(n)=F(n−1)+F(n−2). Naive recursion evaluates the same states repeatedly; its running time is Θ(φn), where φ=(1+√5)/2. Memoization evaluates each distinct state once.

Recursion tree · F(6)

Slide 4. All 25 recursive calls. Magenta identifies repeated fibo(4), blue repeated fibo(3), and red repeated fibo(2) states.

fibo(6)fibo(5)fibo(4)fibo(3)fibo(2)fibo(1)fibo(0)fibo(1)fibo(2)fibo(1)fibo(0)fibo(3)fibo(2)fibo(1)fibo(0)fibo(1)fibo(4)fibo(3)fibo(2)fibo(1)fibo(0)fibo(1)fibo(2)fibo(1)fibo(0)Repeated fibo(4)Repeated fibo(3)Repeated fibo(2)

On a narrow screen, scroll across the tree to follow every branch.

Slide 4 · Recursion tree for fibo(6). Matching outlines mark repeated subproblems.
Memoization and its cache

Slide 5. Repeated fibo(3) and fibo(2) calls read indices 3 and 2 from the array [0,1,1,2,3,5].

fibo(5)fibo(4)fibo(3)reusefibo(3)fibo(2)reusefibo(2)fibo(1)fibo(1)fibo(0)001112233455Findex

On a narrow screen, scroll across the tree to follow every branch.

Slide 5 · The fibo(5) tree reuses fibo(3) and fibo(2) from the same six-entry array.

Memoized Fibonacci · F(5)

Follow the actual left-to-right recursion: call, cache, and reuse.

Why the improvement is linearThere are n+1 states, F(0) through F(n). Each non-base state performs one addition after obtaining its two dependencies. Under the standard unit-cost arithmetic model, this gives O(n) time. Memoization also uses an O(n) recursion stack; the rolling-pair iterative version uses O(1) auxiliary space.
Θ(φⁿ)plain Fibonacci recursion; O(2ⁿ) is a valid loose bound
O(n)memoized/table time
O(n)table space

13.3Top-down and bottom-up DP

Top-down · memoization

Start from the requested input. Recurse only when a needed answer is absent; otherwise return the cached value.

def fib(n, memo):
    if n < 2: return n
    if memo[n] == -1:
        memo[n] = fib(n-1,memo)+fib(n-2,memo)
    return memo[n]
Bottom-up · tabulation

Start with the smallest states, then build solutions for increasing input values.

def fib(n):
    if n < 2: return n
    dp = [0] * (n+1)
    dp[1] = 1
    for i in range(2,n+1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]
Space optimizationKeep only prev2 and prev1. Each new Fibonacci number replaces the older pair, reducing auxiliary space from O(n) to O(1). For an acyclic dependency graph, both approaches implement the same recurrence. Choose an order in which every dependency is available before its dependent state.

13.40/1 knapsack

Each item is either included once or excluded. KP(i,Wc) is the maximum profit using items 1…i with total weight at most Wc.

There are exactly two choices for item i: exclude it and retain capacity Wc, or include it and leave capacity Wc−wi. In both cases, only items 1…i−1 remain available. Taking the maximum covers all feasible subsets. The base case assumes positive item weights, so a zero-capacity knapsack earns zero.

Why greedy profit/weight failsCapacity 15; item 1 has profit 5 and weight 1 (ratio 5), item 2 has profit 15 and weight 15 (ratio 1). Greedy selects item 1 and earns 5, but the optimal 0/1 choice is item 2 with profit 15.
Why greedy selection fails

Capacity 15 · Two indivisible items

Items from the lecture
PropertyItem 1Item 2
Profit515
Weight115
Profit / weight51

Greedy chooses item 1 first

Filled segment: item 1, weight 1.

Item 2 needs 15 units, so it no longer fits.
Greedy profit = 5
The optimal 0/1 choice is item 2 alone: profit 15.
Slide 8 · The two-item greedy example, with capacity 15 and 14 units left unused.
Include or exclude each item

One decision for every item

Item 1
IncludeExclude
Item 2
IncludeExclude
Item 3
IncludeExclude
… Item i
IncludeExclude
… Item n
IncludeExclude
Each item contributes two possibilities. Brute-force search examines O(2ⁿ) subsets.
Slide 10 · Each item has the same two choices: include it or leave it out.

Read the recurrence as a decision

KP(i, Wc) asks: “What is the largest profit I can earn using items 1…i, with total weight at most Wc?”

i
The current item. Only items 1…i are available.
Wc
The capacity available in this subproblem.
wi
The weight of item i.
pi
The profit earned by taking item i.
Knapsack decision flow
Start: KP(i, Wc) — best profit within capacity Wc
  1. No items or no capacity

    i = 0 or Wc = 0

    Return 0.

  2. Otherwise, check the weight.

    Item i is too heavy

    wi > Wc

    Skip item i. Return KP(i−1, Wc).

    The capacity stays the same; only items 1…i−1 remain.

  3. Otherwise, the item fits: wi ≤ Wc.

    Compare the two choices

    Exclude item i

    KP(i−1, Wc)

    Keep capacity Wc.

    Include item i

    pi + KP(i−1, Wc−wi)

    Earn pi and use wi of capacity.

    Return max(exclude, include)

    Both choices continue with items 1…i−1, so item i can be taken only once.

Check the cases in order. Compare include and exclude only when item i fits.

1 · Nothing can be packed

If i = 0 or Wc = 0, there are no items or no capacity left.

KP(i, Wc) = 0

2 · Item i is too heavy

If wi > Wc, skip this item. The capacity stays the same.

KP(i, Wc) = KP(i−1, Wc)

3 · Item i fits

If wi ≤ Wc, compare the two total profits. Include: gain pi and leave capacity Wc−wi. Exclude: gain nothing from this item and keep capacity Wc.

KP(i, Wc) = max(
pi + KP(i−1, Wc−wi),
KP(i−1, Wc))
Why i−1 appears in both choicesAfter deciding whether to take item i, only items 1…i−1 remain. This prevents taking the same item again. An item that exactly fills the available capacity also fits: use the third case when wi = Wc.
Apply the recurrence to the PPT example: KP(4, 8)

Item 4 has weight 5 and profit 6, so it fits in capacity 8.

  • Exclude item 4: KP(3, 8) = 7.
  • Include item 4: 6 + KP(3, 8−5) = 6 + KP(3, 3) = 6 + 2 = 8.

Therefore KP(4, 8) = max(7, 8) = 8. Including item 4 gives the better profit.

O(2ⁿ)brute-force include/exclude tree
O(nW)DP time
O(nW)2D table space
Assumptions and complexityWeights and capacity are nonnegative integers, and item weights are strictly positive. O(nW) is pseudopolynomial: it depends on the numeric capacity W, rather than the number of bits used to encode W. A one-dimensional table uses O(W) space; process capacities in decreasing order to preserve the 0/1 constraint.

13.5Building the knapsack table

Worked example: four items have profits [1,2,5,6] and weights [2,3,4,5], with capacity W=8. State KP(i,w) uses only the first i items and capacity at most w. Each transition reads the previous row, preventing an item from being selected twice.

Bottom-up 0/1 knapsack

Watch each state compare “exclude” with “include”.

Read one transitionAt KP(4,8), excluding item 4 gives KP(3,8)=7. Including it gives 6+KP(3,3)=6+2=8. Therefore store 8. To recover the subset, compare each cell with the cell above: a strictly larger value means this traceback takes the current item and subtracts its weight.
profit = [0, 1, 2, 5, 6]
weight = [0, 2, 3, 4, 5]
kp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
    for w in range(1, W + 1):
        if weight[i] <= w:
            kp[i][w] = max(profit[i] + kp[i-1][w-weight[i]], kp[i-1][w])
        else:
            kp[i][w] = kp[i-1][w]
return kp[n][W]
Top-down memoized version
def knapsack(i, capacity):
    if i == 0 or capacity == 0: return 0
    if memo[i][capacity] != -1: return memo[i][capacity]
    result = knapsack(i-1, capacity)
    if weight[i] <= capacity:
        result = max(result, profit[i] + knapsack(i-1, capacity-weight[i]))
    memo[i][capacity] = result
    return result

13.6Matrix multiplication order matters

Parentheses decide which two matrices to multiply first. That first product determines the dimensions used in the next multiplication, so a different grouping can change the total work.

1 · Read the dimensions and count the work

An m×n matrix has m rows and n columns. For A×B, the number of columns in A must equal the number of rows in B.

m
Rows in the left matrix A.
n
Columns in A and rows in B: the shared dimension.
p
Columns in the right matrix B.
Matrix dimensions and scalar cost
A
m×n
m rows · n columns
B
n×p
n rows · p columns
C = AB
m×p
m rows · p columns
Inner dimensions must match: n = n.
Outer dimensions give the result: m × p.

Why the scalar cost is m × n × p

m × pentries in the result
nproducts per entry
m × n × pscalar multiplications
Slide 21 · Match the inner dimensions, retain the outer dimensions, and multiply m×n×p to count scalar multiplications.
Why m×n×p?

The result has m×p entries. Each entry is a row–column dot product containing n scalar multiplications. Thus the standard method uses (m×p)×n = m×n×p scalar multiplications. We count multiplications here, rather than additions.

2 · Compare the two orders in the PPT

Use the same three matrices throughout. Their sequence stays A1A2A3.

A1
2 × 32 rows, 3 columns
A2
3 × 43 rows, 4 columns
A3
4 × 54 rows, 5 columns

In A1(A2A3), multiply A2A3 first. In (A1A2)A3, multiply A1A2 first. Follow each intermediate result into the second step.

Compare two multiplication orders

A₁: 2×3 · A₂: 3×4 · A₃: 4×5

A₁(A₂A₃)

Multiply A₂A₃ first

Step 1 · A₂ × A₃

(3 × 4) × (4 × 5)

Result: 3 × 5

Cost: 3 × 4 × 5 = 60

Step 2 · A₁ × (A₂A₃)

(2 × 3) × (3 × 5)

Result: 2 × 5

Cost: 2 × 3 × 5 = 30

Total: 60 + 30 = 90

(A₁A₂)A₃

Multiply A₁A₂ first

Step 1 · A₁ × A₂

(2 × 3) × (3 × 4)

Result: 2 × 4

Cost: 2 × 3 × 4 = 24

Step 2 · (A₁A₂) × A₃

(2 × 4) × (4 × 5)

Result: 2 × 5

Cost: 2 × 4 × 5 = 40

Total: 24 + 40 = 64
Both orders produce a 2×5 matrix. Their scalar costs are 90 and 64.
Slide 22 · Each step shows both input shapes, the resulting shape, and its scalar cost. Both orders finish with a 2×5 matrix.
Choose (A₁A₂)A₃ for this example

24 + 40 = 64 scalar multiplications, compared with 60 + 30 = 90 for A1(A2A3). The better grouping saves 26 scalar multiplications.

The intermediate shape is the reason: the two paths first create a 2×4 matrix and a 3×5 matrix respectively. These different shapes change the later cost.

Check the dimensions and arithmetic for all four steps
Scalar multiplication costs for the PPT example
GroupingMultiplicationResultScalar cost
A1(A2A3)A2A3: (3×4)(4×5)3×53×4×5 = 60
A1(A2A3): (2×3)(3×5)2×52×3×5 = 30
(A1A2)A3A1A2: (2×3)(3×4)2×42×3×4 = 24
(A1A2)A3: (2×4)(4×5)2×52×4×5 = 40
Two matrix products, different scalar workBoth groupings perform two matrix multiplications. The totals 90 and 64 count the individual number-by-number multiplications inside those products. Associativity gives A1(A2A3) = (A1A2)A3, so the grouping changes the computation cost while preserving the mathematical result. The matrices remain in the same sequence.

3 · Extend the same idea to four matrices

The next PPT example appends A4: 5×2. Four matrices have five possible parenthesizations:

  1. A1(A2(A3A4))
  2. A1((A2A3)A4)
  3. (A1A2)(A3A4)
  4. (A1(A2A3))A4
  5. ((A1A2)A3)A4

We must compare the total cost of each complete grouping. The matrix-chain DP in the next section reuses the best costs of smaller subchains to find the least-cost grouping.

13.7Matrix-chain dynamic programming

Statem[i,j] is the minimum scalar-multiplication cost for matrices Ai…Aj. The diagonal m[i,i]=0.

Every parenthesization has a final split k: multiply the result of Ai…Ak by the result of Ak+1…Aj. Their dimensions are p[i−1]×p[k] and p[k]×p[j]. The cost is the best cost of each subchain plus the cost of this final multiplication. Try every legal k and retain the cheapest.

m[i,j] = mini≤k<j{m[i,k] + m[k+1,j] + p[i−1]p[k]p[j]}
Matrix-chain split at k = 2

p = [2, 3, 4, 5, 2] · Split at k = 2

  1. 2p[0]
  2. 3p[1]
  3. 4p[2]
  4. 5p[3]
  5. 2p[4]
Left subchain: (A₁A₂)

2×3 · 3×4

Result: 2 × 4

m[1,2] = 24

Right subchain: (A₃A₄)

4×5 · 5×2

Result: 4 × 2

m[3,4] = 40

Combine the results: (2 × 4) × (4 × 2).
Final multiplication cost: 2 × 4 × 2 = 16.
Candidate k = 2: 24 + 40 + 16 = 80
Slide 31–32 · Split at k=2: the two subchains cost 24 and 40, and combining them costs 16.

Fill by increasing chain length

Dimensions p=[2,3,4,5,2].

def matrix_chain_order(p):
    n = len(p) - 1
    m = [[0]*(n+1) for _ in range(n+1)]
    s = [[0]*(n+1) for _ in range(n+1)]
    for length in range(2, n+1):
        for i in range(1, n-length+2):
            j = i + length - 1; m[i][j] = float('inf')
            for k in range(i, j):
                q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]
                if q < m[i][j]: m[i][j], s[i][j] = q, k
    return m, s

Fill by increasing chain length l. For l=3, the start index i takes values 1 and 2: first solve A₁A₂A₃, then A₂A₃A₄. In each case j=i+l−1. The start index chooses the subchain; k chooses a split within that subchain.

The split table s[i,j] stores the k that achieved the minimum. Reconstruct the order recursively by splitting at s[i,j] until each subchain contains one matrix. There are O(n²) intervals and at most O(n) splits per interval, giving O(n³) time.

O(n³)time
O(n²)m and s tables
76optimal cost for all four matrices
Optimal orderA1(A2(A3A4)): first A3A4 costs 40, then A2(A3A4) costs 24 more, and the final multiplication costs 12; total 76.

13.8All-pairs shortest paths: Floyd–Warshall

Given a weighted directed graph G=(V,E), find the shortest distance between every ordered pair of vertices. The input is an adjacency matrix A and the output is a distance matrix D. Unlike a single-source shortest-path problem, every vertex acts as a source.

The four-vertex example

The lecture graph has seven directed edges: 1 → 2 (4), 1 → 4 (8), 2 → 1 (9), 2 → 3 (2), 3 → 1 (6), 3 → 4 (1), and 4 → 1 (2). Direction matters: opposite edges may have different weights.

Initialize D⁽⁰⁾=A. Every diagonal entry is 0. Use the edge weight for a direct edge and ∞ when no direct edge exists. Rows identify the source i and columns identify the destination j.

Weighted directed graph
Four-vertex weighted directed graphSlides 39–57. Directed edges: 1→2=4, 2→1=9, 1→4=8, 4→1=2, 2→3=2, 3→4=1, 3→1=6. The vertex arrangement matches the lecture.49822161234
Slide 39 · The directed graph used in every Floyd–Warshall phase.
Initial distance matrix D⁽⁰⁾
From / To1234
104∞8
2902∞
36∞01
42∞∞0

State and recurrence

Allowed intermediate verticesD⁽ᵏ⁾[i,j] is the shortest distance from i to j when only vertices 1,…,k may appear as intermediate vertices. The endpoints i and j can be any vertices.
D⁽ᵏ⁾[i,j] = min { D⁽ᵏ⁻¹⁾[i,j], D⁽ᵏ⁻¹⁾[i,k] + D⁽ᵏ⁻¹⁾[k,j] }

Keep the best path that avoids k internally, or combine a path from i to k with a path from k to j. Without negative cycles, a shortest path can be chosen without repeating vertices. Splitting a path that uses k gives two subpaths whose intermediate vertices come from 1,…,k−1. This gives the DP recurrence.

Allow one intermediate vertex at a time

Follow the same improvements as the slides, from D⁽⁰⁾ to D⁽⁴⁾.

Four-vertex weighted directed graphSlides 39–57. Directed edges: 1→2=4, 2→1=9, 1→4=8, 4→1=2, 2→3=2, 3→4=1, 3→1=6. The vertex arrangement matches the lecture.49822161234

Updates at each phase

Each phase checks all 16 ordered pairs. The following entries improve; every other entry keeps its previous value.

Distance improvements for the lecture example
Intermediate vertex kImproved entries
1D[2,4]: ∞ → 17; D[3,2]: ∞ → 10; D[4,2]: ∞ → 6
2D[1,3]: ∞ → 6; D[4,3]: ∞ → 8
3D[1,4]: 8 → 7; D[2,1]: 9 → 8; D[2,4]: 17 → 3
4D[2,1]: 8 → 5; D[3,1]: 6 → 3; D[3,2]: 10 → 7
Inspect the matrix after each intermediate vertex
After allowing vertices 1…1: D⁽1⁾
From / To1234
104∞8
290217
361001
426∞0
After allowing vertices 1…2: D⁽2⁾
From / To1234
10468
290217
361001
42680
After allowing vertices 1…3: D⁽3⁾
From / To1234
10467
28023
361001
42680
After allowing vertices 1…4: D⁽4⁾
From / To1234
10467
25023
33701
42680

Final distances and a shortest route

Final shortest-path matrix D⁽⁴⁾
From / To1234
10467
25023
33701
42680
From vertex 2 to vertex 1The direct edge costs 9. The shortest route is 2 → 3 → 4 → 1, with cost 2+1+2=5. At k=3, D[2,4] becomes 3. At k=4, combine that distance with D[4,1]=2 to obtain D[2,1]=5.

Pseudocode and Python

Initialize D = a copy of the adjacency matrix
for k = 1 to n:
    for i = 1 to n:
        for j = 1 to n:
            D[i,j] = min(D[i,j], D[i,k] + D[k,j])
return D
def floyd_warshall(A):
    n = len(A)
    D = [row[:] for row in A]
    for k in range(n):
        for i in range(n):
            for j in range(n):
                D[i][j] = min(D[i][j], D[i][k] + D[k][j])
    return D

inf = float('inf')
A = [[0, 4, inf, 8],
     [9, 0, 2, inf],
     [6, inf, 0, 1],
     [2, inf, inf, 0]]
D = floyd_warshall(A)
print(D[1][0])  # 5: vertex 2 to vertex 1 (zero-based Python indices)
Why k is the outer loopFinish all pairs for one allowed intermediate vertex before advancing k. With no negative cycles, D[k,k]=0 and the kth row and column remain unchanged during their phase, so the recurrence can update one matrix in place. Retaining all n+1 matrices is unnecessary.
O(n³)time: loops over k, i, and j
O(n²)space for the distance matrix
5shortest distance from 2 to 1
Negative edges and negative cyclesNegative-weight edges are allowed. A negative cycle makes the shortest distance unbounded below for any pair whose route can reach that cycle and continue to the destination. After running the algorithm, any D[v,v]<0 indicates a negative cycle; the returned values for affected pairs are not finite shortest-path answers. A pair with no route keeps distance ∞.

13.9Rod-cutting problem

Prices for lengths 1…10 are [1,5,8,9,10,17,17,20,24,30]. For a rod of length n, choose the first piece i and combine its price with the best revenue for n−i.

Assume integer lengths, a listed price for every supported piece length, and no cutting cost. The whole rod must be sold. Choosing i=n sells the rod intact; choosing a smaller i leaves a remainder whose optimal revenue is r[n−i]. Piece lengths may be reused, unlike items in 0/1 knapsack. Set r[0]=0.

rn = max{pn, r1+rn−1, …, rn−1+r1} = max1≤i≤n{pi+rn−i}
Eight cuts of a length-4 rod

Rod length 4 · Prices: p₁=1, p₂=5, p₃=8, p₄=9

Prices appear above the bar; lengths appear inside each piece.

(a) 4Revenue = 9
94
(b) 1 + 3Revenue = 9
11
83
(c) 2 + 2Revenue = 10
52
52
(d) 3 + 1Revenue = 9
83
11
(e) 1 + 1 + 2Revenue = 7
11
11
52
(f) 1 + 2 + 1Revenue = 7
11
52
11
(g) 2 + 1 + 1Revenue = 7
52
11
11
(h) 1 + 1 + 1 + 1Revenue = 4
11
11
11
11
Best: 2 + 2 earns 5 + 5 = 10
Slide 61 · All eight ways to cut the length-4 rod, using the slide’s prices.

Bottom-up revenue construction

Every possible first cut is tested for each increasing rod length.

Why cutting can earn moreFor length 4, selling intact earns 9. Cutting into 2+2 earns 5+5=10, which is optimal. For length 8, the first-cut choice 2 leaves length 6: 5+r[6]=5+17=22. Store the best first piece for each length, then repeatedly subtract it to recover a complete decomposition.
n12345678910
r[n]15810131718222530
best decomposition1232+22+361+6 or 2+2+32+63+610
Rod-cutting recursion

Slide 62. All 16 calls, including repeated subproblems of lengths 3,2,1,0. Children of length n represent the remaining lengths n−1 down to 0.

4321001002100100

On a narrow screen, scroll across the tree to follow every branch.

Slide 62 · Recursive calls for a rod of length 4.
Naive recursion
def cut_rod(p,n):
    if n == 0: return 0
    q = float('-inf')
    for i in range(1,n+1):
        q=max(q,p[i]+cut_rod(p,n-i))
    return q

T(n)=1+Σj=0n−1T(j), exponential time.

Bottom-up
def bottom_up_cut_rod(p,n):
    r=[0]*(n+1)
    for j in range(1,n+1):
        q=float('-inf')
        for i in range(1,j+1):
            q=max(q,p[i]+r[j-i])
        r[j]=q
    return r[n]

O(n²) time and O(n) space.

Memoized top-down rod cutting
def memoized_cut_rod(p,n):
    r=[float('-inf')]*(n+1)
    def aux(length):
        if r[length] >= 0: return r[length]
        if length == 0: q=0
        else: q=max(p[i]+aux(length-i) for i in range(1,length+1))
        r[length]=q; return q
    return aux(n)

13.10Longest common subsequence (LCS)

Given two sequences X and Y, find the longest sequence that appears in both in the same relative order. Characters may be skipped, but they may not be rearranged. LCS measures agreement between sequences and is a useful foundation for comparing text and explaining insertion/deletion edits.

Subsequence: gaps are allowed

ACE is a subsequence of ABCDE: retain A, C, and E and skip B and D. A subsequence preserves order; CA is not a subsequence of ABCDE.

Substring: characters are consecutive

BCD is a substring of ABCDE. ACE is not. The LCS recurrence below solves the subsequence problem; the longest common substring problem needs a different recurrence.

Worked example: the same four letters in both sequences

X · 7 characters
ABCBDAB
Y · 6 characters
BDCABA

An LCS is BCBA, with length 4. The figure shows where its characters appear in each sequence. The DP table below establishes that no longer common subsequence exists.

A common subsequence, in order

Keep order. Gaps are allowed.

One longest common subsequence: BCBA

X1A2B3C4B5D6A7B
YB1D2C3A4B5A6

X positions: 2, 3, 4, 6

Y positions: 1, 3, 5, 6

B C B A · length 4
Green letters spell BCBA in both sequences. Positions are one-based in this illustration; skipped letters remain in their original positions.

Define the state using prefixes

State and base casesL[i,j] is the length of an LCS of X[0:i] and Y[0:j], the first i and j characters. Set L[0,j]=L[i,0]=0: an empty sequence has no nonempty common subsequence.
Empty prefixL[0,j] = L[i,0] = 0

With no characters on one side, the common subsequence is empty.

Last characters matchL[i,j] = 1 + L[i−1,j−1]

Keep this character and extend the diagonal result by one.

Last characters differL[i,j] = max(L[i−1,j], L[i,j−1])

Skip one last character. Keep the better result from above or left.

When the last characters match: an optimal common subsequence can end with that character. Append it to an LCS of the two shorter prefixes. This explains the diagonal dependency and the added 1.

When the last characters differ: they cannot both be the final matched character. Every common subsequence omits at least one of those two positions, so compare the solution that drops X’s last character with the solution that drops Y’s last character. Choose the longer result.

Fill the table row by row, from left to right. Each state uses only the cell above, the cell to the left, or the diagonal cell, all of which are already available. The final length is L[m,n], where m=len(X) and n=len(Y).

LCS table and traceback

LCS length table and traceback

X = ABCBDAB · Y = BDCABA

L[i,j]: rows are X prefixes; columns are Y prefixes
i / j0 · ∅1 · B2 · D3 · C4 · A5 · B6 · A
0 · ∅0000000
1 · A0000111
2 · B0111122
3 · C0112222
4 · B0112233
5 · D0122233
6 · A0122334
7 · B0122344

Read the highlighted path backwards

↖ Match: keep the character.
↑ / ← Mismatch: skip one position.

Characters collected on the walk:

A, B, C, B

Reverse them: BCBA

Stop at an empty prefix. On a tie, this example moves up.

Length 4 proves optimality. The four green diagonal moves recover an LCS.

Start at the bottom-right value: L[7,6] = 4.

Rows are X-prefix lengths and columns are Y-prefix lengths. Follow arrows from L[7,6] back to an empty prefix. Green diagonal moves keep a character; purple moves skip one.
Read a match: L[3,3]

The third characters are both C. Use the diagonal: 1 + L[2,2] = 1 + 1 = 2.

Read a mismatch: L[7,6]

B differs from A. Compare above and left: max(L[6,6], L[7,5]) = max(4,4) = 4. The traceback chooses up on this tie.

Match, compare, and reconstruct

X = ABCBDAB · Y = BDCABA. Build the table, then follow an optimal path backwards.

Follow all eight traceback moves

Character positions in this table are one-based. The collected letters are in reverse order.

Traceback that returns BCBA
MoveCellsDecisionCollected
1L[7,6] → L[6,6]Skip X[7]=B; move up (up wins ties).—
2L[6,6] → L[5,5]Match A: record it and move diagonally.A
3L[5,5] → L[4,5]Skip X[5]=D; move up (up wins ties).A
4L[4,5] → L[3,4]Match B: record it and move diagonally.AB
5L[3,4] → L[3,3]Skip Y[4]=A; move left.AB
6L[3,3] → L[2,2]Match C: record it and move diagonally.ABC
7L[2,2] → L[2,1]Skip Y[2]=D; move left.ABC
8L[2,1] → L[1,0]Match B: record it and move diagonally.ABCB

Stop at L[1,0]. Reverse ABCB to obtain BCBA.

Recover the sequence, not just its length

  1. Start at L[m,n]. If the current characters match, record that character and move diagonally up-left.
  2. If they differ, move to whichever of the upper or left cells has the larger value. Either direction is valid when the values tie; here we choose up.
  3. Stop when either prefix becomes empty. Reverse the recorded characters because the walk collected them from right to left.
Worked resultFor ABCBDAB and BDCABA, the LCS length is 4. The up-on-ties rule returns BCBA. Its positions are 2, 3, 4, 6 in X and 1, 3, 5, 6 in Y (positions here are one-based). BDAB and BCAB are also valid LCS answers: an optimal length does not imply a unique sequence.
def longest_common_subsequence(x, y):
    m, n = len(x), len(y)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if x[i - 1] == y[j - 1]:
                dp[i][j] = 1 + dp[i - 1][j - 1]
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    i, j = m, n
    result = []
    while i > 0 and j > 0:
        if x[i - 1] == y[j - 1]:
            result.append(x[i - 1])
            i -= 1
            j -= 1
        elif dp[i - 1][j] >= dp[i][j - 1]:
            i -= 1
        else:
            j -= 1
    return dp[m][n], ''.join(reversed(result))
O(mn)table construction time
O(mn)space for the table and traceback
O(m+n)traceback time after table construction
Space optimization and its tradeoffIf only the length is needed, keep two rows and use O(min(m,n)) auxiliary space by placing the shorter sequence along the columns. Ordinary traceback requires the full table. Recovering a sequence in linear space needs a more advanced method, such as Hirschberg’s algorithm.

LCS practice problems with explanations

Problem 1 · Trace a small table

Find an LCS of X=ABC and Y=AC. Write the final row of the DP table, including the empty-prefix column.

Show solution

The final row is [0,1,2], and the LCS is AC. Matching A creates length 1; B contributes no match; matching C extends the diagonal prefix result to 2. The B in X can be skipped because a subsequence need not be contiguous.

Problem 2 · A standard worked example

Find an LCS of X=AGGTAB and Y=GXTXAYB. Explain why the answer is valid.

Show solution

The length is 4 and an LCS is GTAB. It appears at positions 2,4,5,6 in X and 1,3,5,7 in Y. The DP recurrence gives L[6,7]=4, establishing that no common subsequence is longer.

Problem 3 · Multiple optimal answers

Find all distinct LCS strings of X=ABC and Y=BAC. Does one traceback necessarily find all of them?

Show solution

The length is 2. The distinct answers are AC and BC. One traceback chooses one branch when the table values tie, so it returns only one answer. To enumerate all answers, follow every optimal branch and remove duplicates; the number of answers can be large.

Problem 4 · Boundary cases

What are the LCS length and sequence for (ABCD,EFGH), (empty string,ABC), and (AAAA,AA)?

Show solution

The first two pairs return length 0 and the empty sequence: there are no shared characters in the first pair and no characters available in the empty input. The third returns length 2 and AA. Matching positions must be used in increasing order, so the same character position cannot be reused.

Problem 5 · Convert one string into another

Using only single-character insertions and deletions, how many edits are needed to transform HEAP into PEA?

Show solution

An LCS is EA, with length 2. Keep these characters, delete H and the final P, then insert P at the beginning: 3 edits. In general, the minimum is m+n−2L because m−L characters must be deleted and n−L inserted. This formula does not apply unchanged when substitutions are permitted as a single edit.

Common mistakesDo not confuse prefix length i with character index i−1. A match adds 1 to the diagonal, not to the cell above. A mismatch takes a maximum; it does not reset the state to zero. Finally, reverse the traceback output before returning it.

13.11Exercises and checks

Use these questions to check both the computed answer and the reasoning behind it.

  1. Improve Fibonacci auxiliary space to O(1) using prev2, prev1, and current.
  2. Can DP improve a single factorial computation? No: the recursion has no overlapping subproblems.
  3. Can DP implement merge sort? Standard merge-sort subproblems are disjoint, not identical, so memoization adds no benefit.
  4. For each DP algorithm, identify the state, recurrence, base case, evaluation order, and returned state.
  5. Derive matrix-chain complexity from the length, start-index, and split-index loops.
  6. Starting from the four-vertex graph, list the entries improved at each Floyd–Warshall phase. Why must the intermediate-vertex loop be outermost?
  7. Compare the direct edge from 2 to 1 with the route 2 → 3 → 4 → 1. What happens if a reachable negative cycle lies on a route?
Check your worked-example answers

F(5)=5. Knapsack returns profit 8 by selecting items 2 and 4 (total weight 8). Matrix-chain multiplication returns cost 76 with order A₁(A₂(A₃A₄)). Floyd–Warshall returns D[2,1]=5 via 2 → 3 → 4 → 1. A rod of length 8 returns revenue 22 using lengths 2 and 6. The LCS example returns BCBA with length 4.

The outer k loop preserves the allowed-intermediate-vertices invariant. The phases improve 3, 2, 3, and 3 entries respectively. A pair that can pass through a negative cycle has no finite minimum distance.

A reliable DP checklistState precisely what each entry means. Establish base cases. Justify that the recurrence considers every legal choice. Evaluate dependencies first. Return the correct state and preserve decisions when the solution itself is required.

Diagram

Scroll to move around an enlarged diagram