Section 5 · 8 min read

Dynamic programming

When and how to use dynamic programming, with the standard GATE formulations: LCS, 0/1 knapsack, matrix chain multiplication, Floyd-Warshall and Bellman-Ford.

On this page
  1. The idea
  2. Longest common subsequence
  3. 0/1 knapsack
  4. Matrix chain multiplication
  5. Floyd-Warshall (all-pairs shortest paths)
  6. Bellman-Ford (single source, negative edges allowed)
  7. Other standard DPs
  8. Worked examples
  9. Common traps
  10. Quick revision

The idea

Dynamic programming (DP) solves a problem by expressing its answer in terms of answers to smaller subproblems, then computing each subproblem once and storing it. It applies when the problem has:

  1. Optimal substructure: an optimal solution is built from optimal solutions of subproblems.
  2. Overlapping subproblems: the same subproblems appear many times in a plain recursion.

Without overlap, plain divide and conquer is enough. Without optimal substructure, no recurrence exists. Naive recursive Fibonacci makes Θ(ϕn)\Theta(\phi^n) calls; storing F(0..n)F(0..n) cuts this to Θ(n)\Theta(n).

Two styles

  • Memoisation (top-down): write the recursion, cache each result the first time it is computed. Only reachable subproblems are solved.
  • Tabulation (bottom-up): fill a table in an order where every dependency is ready. No recursion overhead, and rows that are no longer needed can be discarded to save space.

Recipe

  1. Define the subproblem precisely (what does C[i][j]C[i][j] mean?).
  2. Write the recurrence, including base cases.
  3. Fix an evaluation order.
  4. Running time = number of subproblems ×\times work per subproblem.
  5. Recover the solution itself by storing the choice made at each cell.

Longest common subsequence

A subsequence keeps order but may skip characters. For X=x1..xmX = x_1..x_m and Y=y1..ynY = y_1..y_n, let C[i][j]C[i][j] be the LCS length of the prefixes XiX_i and YjY_j.

C[i][j]={0i=0 or j=0C[i−1][j−1]+1xi=yjmax⁡(C[i−1][j], C[i][j−1])xi≠yjC[i][j] = \begin{cases} 0 & i = 0 \text{ or } j = 0 \\ C[i-1][j-1] + 1 & x_i = y_j \\ \max(C[i-1][j],\ C[i][j-1]) & x_i \ne y_j \end{cases}

Time and space Θ(mn)\Theta(mn). The length alone needs only two rows, Θ(min⁡(m,n))\Theta(\min(m, n)) space. Related: shortest common supersequence length =m+n−LCS= m + n - \text{LCS}; minimum deletions plus insertions to turn XX into YY =m+n−2 LCS= m + n - 2\,\text{LCS}.

0/1 knapsack

Items 1..n1..n with weights wiw_i and values viv_i, capacity WW. K[i][c]K[i][c] is the best value using items 1..i1..i with capacity cc.

K[i][c]={K[i−1][c]wi>cmax⁡(K[i−1][c], K[i−1][c−wi]+vi)otherwiseK[i][c] = \begin{cases} K[i-1][c] & w_i > c \\ \max(K[i-1][c],\ K[i-1][c - w_i] + v_i) & \text{otherwise} \end{cases}

Time Θ(nW)\Theta(nW). This is pseudo-polynomial: polynomial in the value of WW, but exponential in the number of bits of WW. 0/1 knapsack is NP-hard, so this does not contradict anything. With a one-dimensional array, loop cc from WW down to wiw_i so each item is used once; looping upward gives the unbounded knapsack instead.

Subset sum is the same table with boolean entries: S[i][s]=S[i−1][s]∨S[i−1][s−ai]S[i][s] = S[i-1][s] \lor S[i-1][s - a_i].

Matrix chain multiplication

Matrix AiA_i has dimensions pi−1×pip_{i-1} \times p_i. Multiplying a p×qp \times q by a q×rq \times r matrix costs pqrpqr scalar multiplications. m[i][j]m[i][j] is the minimum cost of computing Ai⋯AjA_i \cdots A_j.

m[i][j]={0i=jmin⁡i≤k<j(m[i][k]+m[k+1][j]+pi−1pkpj)i<jm[i][j] = \begin{cases} 0 & i = j \\ \min_{i \le k < j}\big(m[i][k] + m[k+1][j] + p_{i-1}p_kp_j\big) & i < j \end{cases}

Fill by increasing chain length. Θ(n2)\Theta(n^2) subproblems, O(n)O(n) choices each: Θ(n3)\Theta(n^3) time, Θ(n2)\Theta(n^2) space. The number of parenthesisations of nn matrices is the Catalan number Cn−1=1n(2n−2n−1)C_{n-1} = \frac{1}{n}\binom{2n-2}{n-1}, which grows exponentially, so brute force is hopeless.

Floyd-Warshall (all-pairs shortest paths)

d(k)[i][j]d^{(k)}[i][j] is the shortest i→ji \to j path whose intermediate vertices are all in {1..k}\{1..k\}.

d(k)[i][j]=min⁡(d(k−1)[i][j], d(k−1)[i][k]+d(k−1)[k][j])d^{(k)}[i][j] = \min\big(d^{(k-1)}[i][j],\ d^{(k-1)}[i][k] + d^{(k-1)}[k][j]\big)

for k = 1 to V:
    for i = 1 to V:
        for j = 1 to V:
            if d[i][k] + d[k][j] < d[i][j]:
                d[i][j] = d[i][k] + d[k][j]

Time Θ(V3)\Theta(V^3), space Θ(V2)\Theta(V^2) (one matrix can be updated in place). Handles negative edges. A negative cycle shows up as d[i][i]<0d[i][i] < 0. The loop over kk must be outermost. Replacing (min⁡,+)(\min, +) by (∨,∧)(\lor, \land) gives Warshall's transitive closure.

Bellman-Ford (single source, negative edges allowed)

dk(v)d_k(v) is the shortest path to vv using at most kk edges: dk(v)=min⁡(dk−1(v), min⁡(u,v)dk−1(u)+w(u,v))d_k(v) = \min(d_{k-1}(v),\ \min_{(u,v)} d_{k-1}(u) + w(u,v)). A shortest simple path has at most V−1V - 1 edges, so V−1V - 1 rounds suffice.

d[s] = 0; d[v] = infinity for v != s
repeat V - 1 times:
    for each edge (u, v, w):
        if d[u] + w < d[v]: d[v] = d[u] + w
for each edge (u, v, w):
    if d[u] + w < d[v]: report negative cycle

Time O(VE)O(VE). In practice the in-place version may converge earlier, depending on edge order; it can stop as soon as a full pass changes nothing.

Other standard DPs

Problem Subproblem Time
Edit distance prefixes XiX_i, YjY_j Θ(mn)\Theta(mn)
Longest increasing subsequence LIS ending at ii Θ(n2)\Theta(n^2), or Θ(nlog⁡n)\Theta(n\log n) with patience sorting
Coin change (number of ways) amount ss using first ii coins Θ(nS)\Theta(nS)
Rod cutting best value for length jj Θ(n2)\Theta(n^2)
Optimal BST keys i..ji..j Θ(n3)\Theta(n^3)
Travelling salesman (Held-Karp) subset SS, end vertex jj Θ(n22n)\Theta(n^2 2^n)

Worked examples

Worked example 1: LCS table

Find the LCS length of X=X = ACBDAB and Y=Y = BDCABA.

The filled table (rows are prefixes of XX, columns of YY, row 0 and column 0 omitted):

B D C A B A
A 0 0 0 1 1 1
C 0 0 1 1 1 1
B 1 1 1 1 2 2
D 1 2 2 2 2 2
A 1 2 2 3 3 3
B 1 2 2 3 4 4

The answer is the bottom-right cell, 4. Tracing back through diagonal moves gives BDAB, one LCS of length 4.

Worked example 2: 0/1 knapsack

Capacity 7, items (weight, value): (1, 1), (3, 4), (4, 5), (5, 7).

Rows after each item, capacities 0 to 7:

Items used 0 1 2 3 4 5 6 7
{1} 0 1 1 1 1 1 1 1
{1, 2} 0 1 1 4 5 5 5 5
{1, 2, 3} 0 1 1 4 5 6 6 9
{1, 2, 3, 4} 0 1 1 4 5 7 8 9

Answer 9, from items 2 and 3 (weights 3 + 4). The ratio-greedy choice (5, 7) then (1, 1) gives only 8.

Worked example 3: Matrix chain

Dimensions p=(5,10,3,12,5)p = (5, 10, 3, 12, 5), so four matrices.

  • Length 2: m12=5⋅10⋅3=150m_{12} = 5 \cdot 10 \cdot 3 = 150, m23=10⋅3⋅12=360m_{23} = 10 \cdot 3 \cdot 12 = 360, m34=3⋅12⋅5=180m_{34} = 3 \cdot 12 \cdot 5 = 180.
  • m13=min⁡(m23+5⋅10⋅12, m12+5⋅3⋅12)=min⁡(960,330)=330m_{13} = \min(m_{23} + 5\cdot10\cdot12,\ m_{12} + 5\cdot3\cdot12) = \min(960, 330) = 330.
  • m24=min⁡(m34+10⋅3⋅5, m23+10⋅12⋅5)=min⁡(330,960)=330m_{24} = \min(m_{34} + 10\cdot3\cdot5,\ m_{23} + 10\cdot12\cdot5) = \min(330, 960) = 330.
  • m14=min⁡(m24+5⋅10⋅5, m12+m34+5⋅3⋅5, m13+5⋅12⋅5)=min⁡(580,405,630)=405m_{14} = \min(m_{24} + 5\cdot10\cdot5,\ m_{12} + m_{34} + 5\cdot3\cdot5,\ m_{13} + 5\cdot12\cdot5) = \min(580, 405, 630) = 405.

Optimal order (A1A2)(A3A4)(A_1A_2)(A_3A_4) with 405 multiplications.

Worked example 4: Floyd-Warshall

Directed graph on vertices 1 to 4: 1→21\to2 (4), 1→31\to3 (9), 2→32\to3 (3), 2→42\to4 (7), 3→43\to4 (1), 3→13\to1 (6), 4→14\to1 (2).

After k=1k = 1 (paths through vertex 1): d[3][2]=6+4=10d[3][2] = 6 + 4 = 10, d[4][2]=2+4=6d[4][2] = 2 + 4 = 6, d[4][3]=2+9=11d[4][3] = 2 + 9 = 11. After k=2k = 2: d[1][3]=4+3=7d[1][3] = 4 + 3 = 7, d[1][4]=4+7=11d[1][4] = 4 + 7 = 11, d[4][3]=6+3=9d[4][3] = 6 + 3 = 9. After k=3k = 3: d[1][4]=7+1=8d[1][4] = 7 + 1 = 8, d[2][4]=3+1=4d[2][4] = 3 + 1 = 4, d[2][1]=3+6=9d[2][1] = 3 + 6 = 9. After k=4k = 4: d[2][1]=4+2=6d[2][1] = 4 + 2 = 6, d[3][1]=1+2=3d[3][1] = 1 + 2 = 3, d[3][2]=1+6=7d[3][2] = 1 + 6 = 7. Final matrix:

D=(0478603437012690)D = \begin{pmatrix} 0 & 4 & 7 & 8 \\ 6 & 0 & 3 & 4 \\ 3 & 7 & 0 & 1 \\ 2 & 6 & 9 & 0 \end{pmatrix}

Worked example 5: Bellman-Ford needs every pass

Vertices s, a, b, c, t. Edges relaxed in this order each pass: a→ta\to t (4), c→ac\to a (−2-2), a→ca\to c (5), b→cb\to c (−3-3), s→as\to a (6), s→bs\to b (7), b→tb\to t (9).

  • Pass 1: a=6a = 6, b=7b = 7, t=16t = 16 (only edges out of s and b are usable).
  • Pass 2: t=6+4=10t = 6 + 4 = 10, c=min⁡(11,7−3)=4c = \min(11, 7 - 3) = 4.
  • Pass 3: a=4−2=2a = 4 - 2 = 2.
  • Pass 4: t=2+4=6t = 2 + 4 = 6.

Final: a=2a = 2, b=7b = 7, c=4c = 4, t=6t = 6. The worst edge order made the shortest path s→b→c→a→ts \to b \to c \to a \to t (4 edges) need all V−1=4V - 1 = 4 passes.

Common traps

  • 0/1 knapsack in Θ(nW)\Theta(nW) is not a polynomial algorithm.
  • LCS is not the longest common substring; substrings must be contiguous and use a different recurrence (C[i][j]=0C[i][j] = 0 on mismatch).
  • Matrix chain needs pi−1pkpjp_{i-1}p_kp_j, not pipkpjp_ip_kp_j. Write the dimension vector carefully.
  • Floyd-Warshall with kk in an inner loop is wrong.
  • Bellman-Ford pass values depend on edge order; final values do not.
  • Coin change "number of ways" with coins in the outer loop counts combinations; with amounts in the outer loop it counts ordered sequences.

Quick revision

  • DP = optimal substructure + overlapping subproblems.
  • LCS, edit distance: Θ(mn)\Theta(mn). Matrix chain: Θ(n3)\Theta(n^3). 0/1 knapsack: Θ(nW)\Theta(nW), pseudo-polynomial.
  • Floyd-Warshall: Θ(V3)\Theta(V^3), negative edges fine, d[i][i]<0d[i][i] < 0 means a negative cycle.
  • Bellman-Ford: O(VE)O(VE), V−1V - 1 passes, one more pass detects negative cycles.
  • Parenthesisations of nn matrices: Catalan Cn−1C_{n-1} (1, 1, 2, 5, 14, 42 for n=1..6n = 1..6).

Further reading

Test this section