6. Dynamic Programming (DP)
Overlapping subproblems, optimal substructure, Memoization vs Tabulation, space optimization, 0/1 Knapsack, Longest Common Subsequence (LCS), Matrix Chain Multiplication (MCM), and Coin Change across C, C++, and Python
NPTEL / GATE CS / UGC NET Core Subject Key Exam Questions: 0/1 Knapsack recurrence and table filling, LCS length recurrence (), Matrix Chain Multiplication parenthesization (), Coin Change variations, and Space Optimization techniques.
1. Prerequisites & What You Should Know#
Before mastering Dynamic Programming, ensure you understand:
- Recursion Trees & Redundant Work: Being able to trace a recursive call stack and spot repeated subproblem evaluations.
- DAG (Directed Acyclic Graph) Topological Ordering: Every DP problem represents a topological sort over a DAG of dependent states!
- State Representation: Defining what parameters uniquely identify a subproblem (e.g.,
dp[i][w]= max value using first items with capacity ). - Mathematical Induction: Showing that if smaller states are optimal, the transition function preserves optimality for larger states.
2. Conceptual Intuition: The Fibonacci Tragedy & The Sticky Note#
Naive Recursive Fibonacci: fib(5)
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ / \ / \
fib(2) fib(1) f(1) f(0) f(1) f(0)
/ \
f(1) f(0)
Notice: fib(3) is recomputed 2 times!
fib(2) is recomputed 3 times!
Total calls grow exponentially as O(2^n) = O(1.618^n)!
For n = 50, naive recursion takes ~1.12 * 10^15 operations (years of compute).
The Sticky Note (Memoization) Principle#
"Those who cannot remember the past are condemned to repeat it." — George Santayana (Adapted by Richard Bellman)
Whenever you compute an answer to a subproblem for the first time, write it down on a sticky note (lookup table / array). Before performing any recursive calculation, first check if you already have a sticky note for that exact input. If so, return it in constant time!
This collapses the entire exponential tree down into a linear chain of unique states: time!
3. The Two Golden Rules of Dynamic Programming#
A problem can be solved using Dynamic Programming if and only if it satisfies:
- Overlapping Subproblems: A recursive algorithm visits the exact same subproblems over and over, rather than generating brand-new subproblems at every step (which occurs in Divide & Conquer).
- Optimal Substructure: An optimal solution to the global problem can be constructed by combining the optimal solutions of its subproblems.
4. Memoization (Top-Down) vs Tabulation (Bottom-Up)#
Top-Down (Memoization):
[Main Problem] -> Needs Subproblem A & B -> Recurse downwards -> Base cases reached -> Store in cache & return up
Bottom-Up (Tabulation):
[Base Cases: dp[0], dp[1]] -> Iteratively compute dp[2] -> dp[3] -> ... -> [dp[N]: Final Answer]
| Feature | Top-Down (Memoization) | Bottom-Up (Tabulation) |
|---|---|---|
| Formulation | Recursive with cache dictionary/array | Iterative table filling via nested loops |
| Call Stack Overhead | Incurs function call stack depth (risk of stack overflow) | Zero call-stack overhead ( auxiliary stack) |
| State Exploration | Computes only states strictly reachable from root | Systematically computes all states in topological order |
| Space Optimization | Difficult to discard old states | Easy to optimize (e.g. keep only previous row or previous 2 numbers) |
5. Classic DP Formulations Deconstructed#
5.1 0/1 Knapsack Recurrence#
Given items with weights and values , and knapsack capacity :
- Time Complexity:
- Why is it Pseudo-Polynomial? is an integer value, not the number of inputs. The input size of in bits is . Hence runtime is exponential in terms of input bit length ()!
- 1D Space Optimization: By looping capacity in reverse order (from down to ), we ensure each item is used at most once while reducing memory from to !
5.2 Longest Common Subsequence (LCS)#
For strings and :
- Computes the longest sequence of characters that appear in both strings in the same relative order (not necessarily contiguous).
- Backtracking from reconstructs the exact subsequence string in time.
5.3 Matrix Chain Multiplication (MCM)#
Given matrices where has dimension :
- Matrix multiplication is associative: .
- But the number of scalar multiplications depends drastically on the order:
- Multiplying :
- Order : operations.
- Order : operations! (10× slower!)
- Multiplying :
- Recurrence:
- Time Complexity: , Space Complexity: .
6. Real-World Applications#
- Bioinformatics & Genomics: Needleman-Wunsch (global alignment) and Smith-Waterman (local alignment) algorithms align DNA and amino-acid sequences using 2D DP matrices.
- Version Control (
git diff): Myers diff algorithm finds the shortest edit script (LCS) between two file revisions. - Speech Recognition & Telecom: The Viterbi Algorithm decodes hidden states in Hidden Markov Models (HMMs) for natural speech and CDMA mobile phone error-correcting codes.
- Natural Language Processing: Spell checkers, autocomplete suggestions, and search engines compute Levenshtein Minimum Edit Distance via DP.
7. Implementation in C, C++, and Python with Syntax Logic#
#include <stdio.h>
#include <string.h>
/**
* C Syntax Logic Note:
* 1. 1D Array Optimization: Notice dp[w] depends only on previous row dp[w - wt[i]].
* Traversing w BACKWARDS from W down to wt[i] prevents overwriting values from
* the current item, shrinking space from O(nW) to O(W)!
* 2. memset(): Rapidly zeroes the buffer in contiguous memory.
*/
int knapsack01(int W, int wt[], int val[], int n) {
int dp[W + 1];
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; i++) {
// Iterate backwards to ensure 0/1 (single item usage) constraint!
for (int w = W; w >= wt[i]; w--) {
int take = val[i] + dp[w - wt[i]];
if (take > dp[w]) {
dp[w] = take;
}
}
}
return dp[W];
}Practice Quiz
Test your understanding with step-by-step solutions
Practice Quiz
5 questions · 90s per question
Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.