Skip to content

Dynamic Programming

5.1 Principles

Dynamic programming (DP) solves problems by:

  1. Overlapping subproblems: The same subproblems are solved repeatedly.
  2. Optimal substructure: The optimal solution contains optimal solutions to subproblems.

Approaches:

  • Top-down (memoisation): Recursive with caching.
  • Bottom-up (tabulation): Fill a table iteratively from small subproblems to large.

5.2 Memoisation vs. Tabulation

AspectMemoisation (Top-Down)Tabulation (Bottom-Up)
ApproachRecursive with cacheIterative table fill
OrderNatural recursion orderDependency order
SpaceO(n)O(n) stack + O(n)O(n) tableO(n)O(n) table only
OverheadFunction call overheadMinimal
SubproblemsComputes only neededComputes all
Best forWhen not all subproblems neededWhen all needed

When to use which:

  • Use memoisation when the subproblem space is sparse (not all subproblems are needed).
  • Use tabulation when most subproblems are needed (avoids recursion overhead and stack overflow).
  • Both achieve the same asymptotic time complexity.

5.3 Optimal Substructure Proof Technique

To prove that a problem has optimal substructure:

  1. Show that an optimal solution to the problem includes an optimal solution to a subproblem.
  2. Proved by contradiction: if the optimal solution contained a suboptimal sub-solution, replacing it with an optimal one would improve the overall solution.

Example (Shortest Path). If pp is a shortest path from uu to vv and ww is an intermediate vertex on ppThen the subpath of pp from uu to ww is a shortest path from uu to ww.

Proof. If not, there exists a shorter path p"p" from uu to ww. Then pp' concatenated with the subpath of pp from ww to vv would be shorter than ppContradicting that pp is a shortest path. \blacksquare

:::caution Common Pitfall Not all problems have optimal substructure. For example, the longest simple path problem does not: the longest simple path from uu to vv may not contain the longest simple path from uu to an intermediate vertex wwBecause the subpath might share vertices with the rest of the path, creating a non-simple path.

5.4 Common Patterns

1D DP. dp[i]dp[i] depends on dp[j]dp[j] for j<ij < i. Example: Fibonacci, longest increasing subsequence.

2D DP. dp[i][j]dp[i][j] depends on dp[i][j]dp[i'][j'] for (i,j)(i', j') in some set. Example: edit distance, Matrix chain multiplication, longest common subsequence.

Interval DP. dp[i][j]dp[i][j] depends on dp[i][j]dp[i'][j'] where iijji \leq i' \leq j' \leq j. Example: Optimal BST, matrix chain multiplication.

5.5 Worked Example: 0/1 Knapsack

Problem. Given nn items with weights w1,,wnw_1, \ldots, w_n and values v1,,vnv_1, \ldots, v_nAnd a knapsack of capacity WWMaximise the total value without exceeding the capacity.

Recurrence:

dp[i][c]={0ifi=0orc=0dp[i1][c]ifwi>cmax(dp[i1][c],dp[i1][cwi]+vi)ifwicdp[i][c] = \begin{cases} 0 & \mathrm{if} i = 0 \mathrm{ or} c = 0 \\ dp[i-1][c] & \mathrm{if} w_i > c \\ \max(dp[i-1][c], dp[i-1][c - w_i] + v_i) & \mathrm{if} w_i \leq c \end{cases}

Time: O(nW)O(nW). Space: O(nW)O(nW) (can be reduced to O(W)O(W) with 1D array).

Proof of correctness. For each item iiEither we don’t include it (value dp[i1][c]dp[i-1][c]) or we include it (value vi+dp[i1][cwi]v_i + dp[i-1][c - w_i]). The optimal choice is the maximum. The base cases are correct. \blacksquare

Worked Example: 0/1 Knapsack

Items: (w=1,v=1),(w=3,v=4),(w=4,v=5),(w=5,v=7)\\{(w=1, v=1), (w=3, v=4), (w=4, v=5), (w=5, v=7)\\}Capacity W=7W = 7.

Building the DP table (items as rows, capacities 0-7 as columns):

c: 0 1 2 3 4 5 6 7
i=0: 0 0 0 0 0 0 0 0
i=1: 0 1 1 1 1 1 1 1
i=2: 0 1 1 4 5 5 5 5
i=3: 0 1 1 4 5 6 6 9
i=4: 0 1 1 4 5 7 8 9

Maximum value: dp[4][7]=9dp[4][7] = 9 (items 2 and 4: w=3+5=7w = 3 + 5 = 7, v=4+7=11v = 4 + 7 = 11 — let me recalculate).

Correct: items 2 and 3 (w=3+4=7w=3+4=7, v=4+5=9v=4+5=9), or items 1, 2, 4 (w=1+3+5=9>7w=1+3+5=9 > 7Not valid). Items 1, 3 (w=1+4=5w=1+4=5, v=1+5=6v=1+5=6), items 2, 4 (w=3+5=8>7w=3+5=8 > 7). Optimal: items 2 and 3 (w=3+4=7w=3+4=7, v=4+5=9v=4+5=9).

5.6 Worked Example: Edit Distance (Levenshtein Distance)

Problem. Given strings ss of length mm and tt of length nnFind the minimum number of insertions, deletions, and substitutions to transform ss into tt.

Recurrence:

dp[i][j]={jifi=0iifj=0dp[i1][j1]ifs[i]=t[j]1+min(dp[i1][j],dp[i][j1],dp[i1][j1])ifs[i]t[j]dp[i][j] = \begin{cases} j & \mathrm{if} i = 0 \\ i & \mathrm{if} j = 0 \\ dp[i-1][j-1] & \mathrm{if} s[i] = t[j] \\ 1 + \min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) & \mathrm{if} s[i] \neq t[j] \end{cases}

Where the three cases in the minimum are: delete from ssInsert into ssSubstitute in ss.

Time: O(mn)O(mn). Space: O(mn)O(mn) (can be reduced to O(min(m,n))O(\min(m,n))).

Worked Example: Edit Distance

Compute the edit distance between “kitten” and “sitting”.

Building the DP table:

"" s i t t i n g
"" 0 1 2 3 4 5 6 7
k 1 1 2 3 4 5 6 7
i 2 2 1 2 3 4 5 6
t 3 3 2 1 2 3 4 5
t 4 4 3 2 1 2 3 4
e 5 5 4 3 2 2 3 4
n 6 6 5 4 3 3 2 3

Edit distance: dp[6][7]=3dp[6][7] = 3.

Transform: kitten → sitten (substitute k→s) → sittin (substitute e→i) → sitting (insert g).

5.7 Worked Example: Matrix Chain Multiplication

Problem. Given matrices A1,A2,,AnA_1, A_2, \ldots, A_n where AiA_i has dimensions pi1×pip_{i-1} \times p_iFind the parenthesisation that minimises the total number of scalar multiplications.

Recurrence:

dp[i][j]={0ifi=jminik<j(dp[i][k]+dp[k+1][j]+pi1pkpj)ifi<jdp[i][j] = \begin{cases} 0 & \mathrm{if} i = j \\ \min_{i \leq k < j} (dp[i][k] + dp[k+1][j] + p_{i-1} p_k p_j) & \mathrm{if} i < j \end{cases}

Time: O(n3)O(n^3). Space: O(n2)O(n^2).

Proof of correctness. The optimal parenthesisation of AiAjA_i \cdots A_j splits at some position kk: (AiAk)(Ak+1Aj)(A_i \cdots A_k)(A_{k+1} \cdots A_j). The cost is the cost of the left subproduct plus the cost of the right subproduct plus the cost of multiplying the resulting matrices (pi1pkpjp_{i-1} \cdot p_k \cdot p_j scalar multiplications). The optimal kk minimises this total. \blacksquare

Worked Example: Matrix Chain Multiplication

Matrices: A1A_1 (10×3010 \times 30), A2A_2 (30×530 \times 5), A3A_3 (5×605 \times 60). Dimensions: p=[10,30,5,60]p = [10, 30, 5, 60].

dp[1][1]=dp[2][2]=dp[3][3]=0dp[1][1] = dp[2][2] = dp[3][3] = 0.

dp[1][2]=p0p1p2=10×30×5=1500dp[1][2] = p_0 p_1 p_2 = 10 \times 30 \times 5 = 1500. Split at k=1k=1: (A1A2)(A_1 A_2).

dp[2][3]=p1p2p3=30×5×60=9000dp[2][3] = p_1 p_2 p_3 = 30 \times 5 \times 60 = 9000. Split at k=2k=2: (A2A3)(A_2 A_3).

dp[1][3]dp[1][3]: Try k=1k=1: dp[1][1]+dp[2][3]+10×30×60=0+9000+18000=27000dp[1][1] + dp[2][3] + 10 \times 30 \times 60 = 0 + 9000 + 18000 = 27000. Try k=2k=2: dp[1][2]+dp[3][3]+10×5×60=1500+0+3000=4500dp[1][2] + dp[3][3] + 10 \times 5 \times 60 = 1500 + 0 + 3000 = 4500.

Minimum: dp[1][3]=4500dp[1][3] = 4500Split at k=2k=2: (A1(A2A3))(A_1(A_2 A_3)).

5.8 Worked Example: Longest Common Subsequence

Problem. Given sequences X=(x1,,xm)X = (x_1, \ldots, x_m) and Y=(y1,,yn)Y = (y_1, \ldots, y_n)Find the LCS.

Recurrence:

dp[i][j]={0ifi=0orj=0dp[i1][j1]+1ifxi=yjmax(dp[i1][j],dp[i][j1])ifxiyjdp[i][j] = \begin{cases} 0 & \mathrm{if} i = 0 \mathrm{ or} j = 0 \\ dp[i-1][j-1] + 1 & \mathrm{if} x_i = y_j \\ \max(dp[i-1][j], dp[i][j-1]) & \mathrm{if} x_i \neq y_j \end{cases}

Time: O(mn)O(mn). Space: O(mn)O(mn) (can be reduced to O(min(m,n))O(\min(m,n)) for the length only).

Proof of correctness. If xi=yjx_i = y_jAny LCS of X[1..i]X[1..i] and Y[1..j]Y[1..j] must include xix_i So LCS=1+LCS(X[1..i1],Y[1..j1])\mathrm{LCS} = 1 + \mathrm{LCS}(X[1..i-1], Y[1..j-1]). If xiyjx_i \neq y_jThe LCS either Excludes xix_i or excludes yjy_jGiving the max of the two subproblems. \blacksquare

5.9 Worked Example: Coin Change

Problem. Given coin denominations d1,,dnd_1, \ldots, d_n and a target amount MMFind the minimum number of coins needed.

Recurrence:

dp[c]={0ifc=0mini:dic(dp[cdi]+1)ifc>0dp[c] = \begin{cases} 0 & \mathrm{if} c = 0 \\ \min_{i: d_i \leq c}(dp[c - d_i] + 1) & \mathrm{if} c > 0 \end{cases}

Time: O(nM)O(nM). Space: O(M)O(M).

Proof of correctness. To make change for amount c>0c > 0The last coin used must be some dicd_i \leq c. The remaining amount is cdic - d_iAnd the optimal solution for cc uses 1+dp[cdi]1 + dp[c - d_i] coins. Taking the minimum over all valid did_i gives the optimal solution. \blacksquare

Worked Example: Coin Change

Denominations: 1,5,10,25\\{1, 5, 10, 25\\}. Target: M=63M = 63.

Bottom-up computation:

  • dp[0]=0dp[0] = 0
  • dp[1..4]=dp[c1]+1=cdp[1..4] = dp[c - 1] + 1 = c (use pennies)
  • dp[5]=min(dp[4]+1,dp[0]+1)=1dp[5] = \min(dp[4]+1, dp[0]+1) = 1 (use a nickel)
  • dp[10]=min(dp[9]+1,dp[5]+1,dp[0]+1)=1dp[10] = \min(dp[9]+1, dp[5]+1, dp[0]+1) = 1 (use a dime)
  • dp[25]=1dp[25] = 1 (use a quarter)
  • dp[50]=2dp[50] = 2 (use two quarters)
  • dp[63]=min(dp[62]+1,dp[58]+1,dp[53]+1,dp[38]+1)dp[63] = \min(dp[62]+1, dp[58]+1, dp[53]+1, dp[38]+1)

Working backwards: dp[63]=dp[38]+1=dp[13]+2=dp[3]+3=6dp[63] = dp[38] + 1 = dp[13] + 2 = dp[3] + 3 = 6.

Solution: 2 quarters + 1 dime + 3 pennies = 25+25+10+1+1+1=6325 + 25 + 10 + 1 + 1 + 1 = 63. 6 coins.

5.10 Worked Example: Longest Increasing Subsequence

Problem. Given a sequence a1,,ana_1, \ldots, a_nFind the length of the longest strictly increasing subsequence (not necessarily contiguous).

Recurrence: dp[i]=1+maxdp[j]:j<i and aj<aidp[i] = 1 + \max\\{dp[j] : j \lt i \mathrm{~and~} a_j \lt a_i\\}With dp[i]=1dp[i] = 1 if no such jj exists.

Time: O(n2)O(n^2). Space: O(n)O(n).

Worked Example: Longest Increasing Subsequence

Find the LIS of [10,22,9,33,21,50,41,60,80][10, 22, 9, 33, 21, 50, 41, 60, 80].

dp[0]=1dp[0] = 1 (just 10) dp[1]=dp[0]+1=2dp[1] = dp[0] + 1 = 2 (10, 22) dp[2]=1dp[2] = 1 (just 9) dp[3]=dp[1]+1=3dp[3] = dp[1] + 1 = 3 (10, 22, 33) dp[4]=dp[0]+1=2dp[4] = dp[0] + 1 = 2 (10, 21) dp[5]=dp[3]+1=4dp[5] = dp[3] + 1 = 4 (10, 22, 33, 50) dp[6]=dp[4]+1=3dp[6] = dp[4] + 1 = 3 (10, 21, 41) dp[7]=dp[5]+1=5dp[7] = dp[5] + 1 = 5 (10, 22, 33, 50, 60) dp[8]=dp[7]+1=6dp[8] = dp[7] + 1 = 6 (10, 22, 33, 50, 60, 80)

LIS length: 6.

Patience sorting approach (O(nlogn)O(n \log n)): Maintain piles. For each card, place on the leftmost pile whose top card is \geq the current card, or start a new pile on the right if no such pile exists. The number of piles equals the LIS length.

:::