Dynamic Programming
DP solves problems by storing solutions to overlapping subproblems. GATE tests classic DP problems with a focus on recurrences and complexities.
Key Points
- ·Two properties: optimal substructure + overlapping subproblems
- ·Top-down (memoisation): recursive + cache; Bottom-up (tabulation): iterative DP table
- ·LCS: O(mn) time and space
- ·LIS: O(n²) naive DP; O(n log n) with patience sorting
- ·0/1 Knapsack: O(nW) pseudo-polynomial
- ·Matrix Chain Multiplication: O(n³)
What Makes a Problem "DP-able"?
Two conditions must both hold:
1. Optimal Substructure: The optimal solution to the whole problem contains optimal solutions to subproblems.
Example: The shortest path from A to C through B — the A→B portion must also be the shortest path from A to B. (If there were a shorter A→B path, we'd use that instead.)
2. Overlapping Subproblems: The same subproblems are solved multiple times.
Example: Fibonacci — fib(5) calls fib(4) and fib(3). fib(4) also calls fib(3). Without memoisation, fib(3) is computed twice.
Top-Down vs Bottom-Up
Top-Down (Memoisation): Write the natural recursive solution. Add a cache (array/hash map) to remember already-computed answers.
memo = {}
def fib(n):
if n in memo: return memo[n]
if n <= 1: return n
memo[n] = fib(n-1) + fib(n-2)
return memo[n]
Bottom-Up (Tabulation): Fill a table iteratively, starting from the smallest subproblems.
dp[0] = 0, dp[1] = 1
for i from 2 to n:
dp[i] = dp[i-1] + dp[i-2]
Both give the same answer. Bottom-up avoids recursion stack overflow and is usually faster in practice.
LCS — Longest Common Subsequence
Given strings X = "ABCBDAB" and Y = "BDCAB", find the longest sequence of characters appearing in both (not necessarily contiguous).
LCS = "BCAB" or "BDAB", length = 4.
Recurrence:
dp[i][j] = length of LCS of X[1..i] and Y[1..j]
If X[i] == Y[j]: dp[i][j] = dp[i-1][j-1] + 1
Else: dp[i][j] = max(dp[i-1][j], dp[i][j-1])
Filling the table for X="ABC", Y="AC":
dp[i][j]: "" A C
"" 0 0 0
A 0 1 1
B 0 1 1
C 0 1 2 ← LCS length = 2 ("AC")
Time: O(mn), Space: O(mn) or O(min(m,n)) with rolling array.
0/1 Knapsack
You have a knapsack with capacity W. Each item i has weight wᵢ and value vᵢ. You cannot break items. Maximise total value.
Subproblem: dp[i][w] = max value using first i items with capacity w.
If wᵢ > w: dp[i][w] = dp[i-1][w] (can't include item i)
Else: dp[i][w] = max(dp[i-1][w], (exclude)
dp[i-1][w-wᵢ] + vᵢ) (include)
Example: Capacity W=5, items: (w=2,v=3), (w=3,v=4), (w=2,v=2)
dp[0][0..5] = [0,0,0,0,0,0] (no items → value 0)
Item 1 (w=2,v=3):
dp[1][0]=0, dp[1][1]=0, dp[1][2]=3, dp[1][3]=3, dp[1][4]=3, dp[1][5]=3
Item 2 (w=3,v=4):
dp[2][3]=max(3, 0+4)=4, dp[2][4]=max(3, dp[1][1]+4)=4,
dp[2][5]=max(3, dp[1][2]+4)=max(3,7)=7 ← take both items!
Time: O(nW). Pseudo-polynomial — W can be exponentially large in input bit size.
Matrix Chain Multiplication
Given matrices A₁A₂...Aₙ, find the optimal parenthesisation to minimise scalar multiplications.
Key fact: (A × B) × C has different cost from A × (B × C).
Subproblem: dp[i][j] = minimum cost to multiply matrices i through j.
dp[i][i] = 0 (single matrix, no multiplication)
For chains of length L = 2 to n:
For each i:
j = i + L - 1
dp[i][j] = min over k (i ≤ k < j):
dp[i][k] + dp[k+1][j] + p[i-1]×p[k]×p[j]
Where p[i-1], p[k], p[j] are dimensions (matrix i has dimensions p[i-1] × p[i]).
Time: O(n³), Space: O(n²).
Edit Distance (Levenshtein)
Minimum number of insert/delete/replace operations to transform string X into Y.
dp[i][j] = edit distance between X[1..i] and Y[1..j]
If X[i] == Y[j]: dp[i][j] = dp[i-1][j-1] (no cost)
Else: dp[i][j] = 1 + min(
dp[i-1][j], // delete from X
dp[i][j-1], // insert into X
dp[i-1][j-1] // replace
)
Example: X="CAT", Y="CAR"
dp[i][j]: "" C A R
"" 0 1 2 3
C 1 0 1 2
A 2 1 0 1
T 3 2 1 1 ← edit distance = 1 (replace T with R)
Quick Check
Q1. LCS of "GATE" and "DATE" — what is its length?
Answer: LCS = "ATE" → length 3.
Q2. 0/1 Knapsack with capacity 4, items (w=1,v=1), (w=3,v=4), (w=4,v=5). What is max value?
Answer: Take items 2 (w=3,v=4) and 1 (w=1,v=1) → weight=4, value=5. Or take item 3 (w=4,v=5) → same weight, same value.
Q3. Why is 0/1 Knapsack called "pseudo-polynomial"?
Answer: O(nW) time looks polynomial, but W can be represented with log W bits — so the algorithm is exponential in the input size (number of bits to represent W).
Key Formulas
- LCS recurrence: dp[i][j] = dp[i-1][j-1]+1 if match, else max(dp[i-1][j], dp[i][j-1])
- 0/1 Knapsack: dp[i][w] = max(dp[i-1][w], val[i] + dp[i-1][w-wt[i]])
- Matrix Chain: dp[i][j] = min_k { dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j] }
- Edit Distance: dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
GATE Exam Tips
- ★0/1 Knapsack is NP-hard (pseudo-polynomial, not truly polynomial)
- ★LCS vs Edit Distance: LCS counts common characters; edit distance counts transformation steps
- ★Matrix Chain: n matrices → n-1 splits to try at each step → O(n³)
- ★Longest path in DAG: O(V+E) using DP on topological order
Finished reading this topic?
Mark it complete to track your study progress.