Introduction

Dynamic programming (DP) solves an optimisation (or counting) problem by combining answers to smaller overlapping subproblems. Where plain recursion recomputes the same subproblem many times, DP stores each answer once and reuses it. Where a greedy algorithm commits to one local choice, DP considers every feasible choice at a subproblem and keeps the best.

The GATE syllabus names DP as a design technique. This note states the two ingredients (optimal substructure and overlapping subproblems), shows the memoized and bottom-up styles on the classical rod-cutting problem, and records how to reconstruct a solution from a filled table. Concrete applications (LCS, knapsack, matrix-chain, Floyd-Warshall) live in later notes.

Prerequisites

Core concepts

Two ingredients

  1. Optimal substructure. An optimal solution of the instance contains optimal solutions of smaller instances. Formally: if you cut an optimal rod solution after the first piece of length ii, the remaining length n−in-i is itself cut optimally.
  2. Overlapping subproblems. The natural recursion revisits the same smaller instance many times. In many polynomial-time examples here the number of distinct states is polynomial while the recursion tree has exponential size. Overlap means repeated states; DP time is determined by the actual state and transition counts, which need not be polynomial.

Divide and conquer also needs optimal (or exact) substructure, but its subproblems are independent: merge sort never sorts the same index range twice. Memoizing merge sort therefore gains nothing. DP is the right tool precisely when the same subproblem recurs.

Two implementation styles

StyleIdeaTypical timeExtra space
Top-down (memoization)Recurse as usual; cache every return value in a table keyed by the subproblemSame as bottom-up, up to recursion overheadTable of all reached states, plus the call stack
Bottom-up (tabulation)Enumerate subproblems from smallest to largest and fill a table in that orderΘ(number of states×work per state)\Theta(\text{number of states} \times \text{work per state})The table (often compressible to a few rows)

Both compute the same values. Bottom-up makes the fill order explicit and avoids stack overflow; top-down computes only states that are actually needed.

Fill order. A bottom-up table must be filled so that every state is computed after all the states it reads. If dp[i][j]dp[i][j] reads dp[i−1][⋅]dp[i-1][\cdot] and dp[i][j−1]dp[i][j-1] (LCS, edit distance, knapsack), row-major order works. If it reads shorter intervals dp[i][k]dp[i][k] and dp[k+1][j]dp[k+1][j] (matrix-chain), fill by increasing interval length j−ij - i; plain row-major order from the top row would read cells that are not yet filled.

State, transition, base case

A DP solution is completely described by:

  • the state (what identifies a subproblem),
  • the recurrence / transition (how a state's answer is built from smaller ones),
  • the base cases,
  • and, when a concrete solution is required, the reconstruction rule (often an auxiliary choice table).

Writing these four items before coding is the reliable habit.

Algorithms and pseudocode

Pseudocode uses 1-based arrays, <- for assignment and A[i..j] for the subarray from position ii to position jj.

Rod cutting

Input. A rod of integer length n≥1n \ge 1 and a price list p[1..n]p[1..n], where p[i]p[i] is the price of a piece of length ii.

Output. The maximum revenue rnr_n obtainable by cutting the rod into integer-length pieces (or leaving it uncut). Cuts are free.

Optimal substructure gives the recurrence

rn=max⁡1≤i≤n(p[i]+rn−i),r0=0.r_n = \max_{1 \le i \le n}\bigl(p[i] + r_{n-i}\bigr), \qquad r_0 = 0.

Here ii is the length of the first piece; the residual rod of length n−in-i is solved optimally. The case i=ni = n is the uncut rod.

Text
MEMOIZED-CUT-ROD(p, n)
1  let r[0..n] be a new array
2  for i <- 0 to n
3      r[i] <- -infinity
4  return MEMOIZED-CUT-ROD-AUX(p, n, r)

MEMOIZED-CUT-ROD-AUX(p, n, r)
1  if r[n] != -infinity then
2      return r[n]
3  if n = 0 then
4      q <- 0
5  else q <- -infinity
6      for i <- 1 to n
7          q <- max(q, p[i] + MEMOIZED-CUT-ROD-AUX(p, n - i, r))
8  r[n] <- q
9  return q
Text
BOTTOM-UP-CUT-ROD(p, n)
1  let r[0..n] be a new array
2  r[0] <- 0
3  for j <- 1 to n
4      q <- -infinity
5      for i <- 1 to j
6          q <- max(q, p[i] + r[j - i])
7      r[j] <- q
8  return r[n]

Time / space. Bottom-up: the outer loop runs nn times and the inner loop up to jj times, so Θ(n2)\Theta(n^2) time and Θ(n)\Theta(n) space. Memoized: the same asymptotic bounds; each of the n+1n+1 distinct lengths is solved once, and solving length jj costs Θ(j)\Theta(j). Naive recursion without a table is Θ(2n)\Theta(2^n) (CLRS 3e, Section 15.1).

Reconstruction. Store with each r[j]r[j] the first-piece length s[j]s[j] that attained the maximum. Then emit pieces by walking n, n−s[n], n−s[n]−s[n−s[n]], …n,\, n-s[n],\, n-s[n]-s[n-s[n]],\, \ldots until length 0.

Text
EXTENDED-BOTTOM-UP-CUT-ROD(p, n)
1  let r[0..n] and s[0..n] be new arrays
2  r[0] <- 0
3  for j <- 1 to n
4      q <- -infinity
5      for i <- 1 to j
6          if q < p[i] + r[j - i] then
7              q <- p[i] + r[j - i]
8              s[j] <- i
9      r[j] <- q
10 return (r, s)

PRINT-CUT-ROD-SOLUTION(p, n)
1  (r, s) <- EXTENDED-BOTTOM-UP-CUT-ROD(p, n)
2  while n > 0
3      print s[n]
4      n <- n - s[n]

Edge cases. n=0n = 0 returns revenue 0 and no pieces. The full rod length must be consumed, even with negative prices. For n=2n=2, p[1]=−1p[1]=-1, p[2]=−100p[2]=-100, the best cut is 1+11+1, with revenue −2-2, not the uncut piece. The memoized algorithm uses a reserved −∞-\infty sentinel, distinct from all finite revenues; the examples here use positive prices. Ties among several optimal first pieces are broken by the if q < ... test (the smallest ii wins); any tie-break is correct for the revenue.

Mathematical explanation

Why the recurrence is correct

Let RR be any optimal cutting of a rod of length nn, and let the leftmost piece have length ii. The remaining pieces form a cutting of length n−in-i. If that residual cutting were suboptimal, replacing it by an optimal one would raise the total revenue, contradicting optimality of RR. Hence some optimal solution begins with a first piece of length ii followed by an optimal solution of length n−in-i. Maximising over every feasible ii yields rnr_n.

Overlap count

A naive recursive call for length nn spawns calls for every smaller length; those in turn spawn further calls. If T(n)T(n) counts all calls, T(0)=1T(0) = 1 and T(n)=1+∑j=0n−1T(j)T(n) = 1 + \sum_{j=0}^{n-1} T(j), so T(n)=2nT(n) = 2^n. The recursion tree has 2n−12^{n-1} leaves for n≥1n \ge 1: each composition of nn as an ordered sum of positive integers is one root-to-leaf path. Distinct subproblems, however, are only the n+1n+1 lengths 0,1,…,n0,1,\ldots,n. Memoization collapses the exponential tree onto a linear table.

Pseudo-polynomial time

Running time is measured against the input size in bits. A number WW occupies about lg⁡W\lg W bits, so a bound like Θ(nW)\Theta(nW) (0/1 knapsack, coin change for amount WW) is polynomial in the value WW but exponential in its length: doubling the number of bits of WW squares WW. Such an algorithm is called pseudo-polynomial. It is fast when the numbers are small, and it does not contradict the NP-hardness of 0/1 knapsack. By contrast, rod cutting's Θ(n2)\Theta(n^2) bound is polynomial in the input size, since the input lists nn prices.

DP versus greedy versus divide and conquer

Optimal substructureOverlapping subproblemsChoice at a state
DPrequiredrequired (else memoization is pointless)try all, keep best
Greedyrequirednot neededcommit to one local rule
Divide and conquerrequired for correctness of the combineabsent (independent subproblems)fixed split, then combine

A density-greedy cut for rod cutting fails on the prices p=[1,20,33,36]p = [1, 20, 33, 36] for n=4n = 4: density picks length 3 first (revenue 33+p[1]=3433 + p[1] = 34), while two pieces of length 2 give 4040 (CLRS Exercise 15.1-2).

Worked examples

Rod cutting for n=4n = 4

Prices (CLRS Figure 15.1 style): p[1..4]=1,5,8,9p[1..4] = 1, 5, 8, 9.

Bottom-up fill:

jjcandidates p[i]+r[j−i]p[i]+r[j-i]r[j]r[j]s[j]s[j]
1p[1]=1p[1]=111
2p[1]+r[1]=2p[1]+r[1]=2, p[2]=5p[2]=552
31+5=61+5=6, 5+1=65+1=6, p[3]=8p[3]=883
41+8=91+8=9, 5+5=105+5=10, 8+1=98+1=9, p[4]=9p[4]=9102

Revenue r4=10r_4 = 10, first piece s[4]=2s[4] = 2, residual length 2 with s[2]=2s[2] = 2: pieces 2+22+2. The uncut rod is worth only 9.

Naive recursion tree for rod cutting of length 4 beside the filled revenue table
Figure 1. Left: a partial unmemoized recursion tree (remaining subtrees omitted) for rod cutting of length 4 (subproblem n=2n=2 appears more than once). Right: the bottom-up table r[0..4]=0,1,5,8,10r[0..4] = 0,1,5,8,10 that solves each length once.

Fibonacci as a counting DP

The recurrence F0=0F_0 = 0, F1=1F_1 = 1, Fn=Fn−1+Fn−2F_n = F_{n-1} + F_{n-2} for n≥2n \ge 2 has optimal-substructure form (exactly two subproblems) and massive overlap. Bottom-up fills an array in Θ(n)\Theta(n) time; with the golden ratio ϕ=(1+5)/2\phi=(1+\sqrt{5})/2, the naive tree has Θ(ϕn)\Theta(\phi^n) nodes. This is a counting / evaluation DP rather than an optimisation DP, but the same two ingredients apply (CLRS Exercise 15.1-5).

Counting DP: ways to pay an amount

Count the ways to pay 5 with coins of value 1, 2 and 5 (unlimited supply).

  • Combinations (order does not matter, 2+2+1=1+2+22+2+1 = 1+2+2): process coins in the outer loop, ways[a]+=ways[a−c]ways[a] \mathrel{+}= ways[a - c] for a=c,…,5a = c, \ldots, 5, after initializing ways[0..5]=0ways[0..5]=0, then ways[0]=1ways[0]=1. Result 4: {5},{2,2,1},{2,1,1,1},{1,1,1,1,1}\{5\}, \{2,2,1\}, \{2,1,1,1\}, \{1,1,1,1,1\}.
  • Ordered sequences (2+2+12+2+1 and 1+2+21+2+2 are different): independently reset all entries ways[0..5] to zero and set ways[0]=1; put amounts in the outer loop, ways[a]=∑c≤aways[a−c]ways[a] = \sum_{c \le a} ways[a - c]. Result 9.

Loop order versus meaning. The same one-line recurrence counts different things depending on which loop is outside. Coins outside gives combinations (4); amounts outside gives ordered sequences (9).

The minimum number of coins uses the same shape with min⁡\min: c(0)=0c(0) = 0, c(a)=1+min⁡d≤ac(a−d)c(a) = 1 + \min_{d \le a} c(a-d). For coins 1, 2 and 5 and amount 11 it gives 3 (5+5+15+5+1).

Counting calls in naive recursion

Plain recursive Fibonacci (fib(n) calls fib(n-1) and fib(n-2), with fib(0) and fib(1) as base cases) makes 2Fn+1−12F_{n+1} - 1 calls in total. For n=5n = 5 that is 15 calls: fib(2) runs 3 times, fib(1) 5 times and fib(0) 3 times. With memoization each of fib(0) to fib(5) is computed once, in Θ(n)\Theta(n) total time.

When not to use DP

  • Independent subproblems (binary search, merge sort): divide and conquer.
  • A proved greedy-choice property (activity selection, Huffman, MST): greedy is enough and usually faster.
  • Simple-path constraints require care. With nonnegative weights, or when no relevant negative cycle exists, shortest-walk optimal substructure applies: subpaths of shortest paths are shortest. For arbitrary signed weights, shortest and longest simple paths may require visited-set states to enforce simplicity; those states can be exponential. A state consisting only of endpoints cannot in general encode the interaction between already visited vertices.

Important properties and observations

  • The table size equals the number of distinct states. If a state needs two indices i,ji,j with 1≤i≤j≤n1 \le i \le j \le n, expect Θ(n2)\Theta(n^2) states (matrix-chain, LCS with two strings of length nn).
  • Time is (states) times (work to compute one state). Rod cutting: nn states, O(n)O(n) work each, Θ(n2)\Theta(n^2) total.
  • Space can often drop to the last row or two when reconstruction is not required (LCS length alone needs O(min⁡(m,n))O(\min(m,n)) space).
  • Reconstruction needs either an explicit choice table or a second pass that re-evaluates the recurrence and follows the argmax.
  • Always state whether the problem asks for the optimal value, one optimal solution, or the number of optimal solutions: the three variants share a table shape but differ in the transition and in what is stored.

Common misconceptions.

  • "DP is recursion with a table." Memoization is that; bottom-up DP never needs a recursive call.
  • "Greedy is a special case of DP." Greedy commits without comparing alternatives; DP compares them. They share optimal substructure, not the choice rule.
  • "Every overlapping recursion is DP." If the goal is not built from sub-answers by a fixed combine (for example, searching for any feasible path with side constraints that break optimal substructure), caching alone does not yield a correct algorithm.

GATE examination perspective

  • IDENTIFY THE STATE. Given a verbal problem, first fix the dimension of the DP table and the meaning of dp[i][j]dp[i][j].
  • WRITE THE RECURRENCE FIRST. Fill-order and complexity follow from the recurrence; coding without it invites off-by-one errors.
  • COUNT THE STATES AND THE TRANSITIONS. Time =Θ(#states×#transitions per state)= \Theta(\#\text{states} \times \#\text{transitions per state}).
  • RECONSTRUCTION. Be ready to recover the cuts, the parenthesization or the subsequence from the choice table.
  • GREEDY COUNTEREXAMPLE. A one-line instance where density (or another local rule) fails shows why DP is needed.
  • CALL COUNTS. "How many times is f(k) called?" Draw the first levels of the naive tree or set up a count recurrence; memoization reduces each count to one computation.
  • WAYS TO PAY. Coins in the outer loop count combinations; amounts in the outer loop count ordered sequences.
  • PSEUDO-POLYNOMIAL. Θ(nW)\Theta(nW) is not polynomial in the input size when WW is given in binary.

Summary

Sources

  1. T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 3rd ed., MIT Press, 2009: Chapter 15, Section 15.1 (rod cutting) and Section 15.3 (elements of dynamic programming); Exercises 15.1-2 (greedy density counterexample) and 15.1-5 (Fibonacci).

Next steps