Learn how Dynamic Programming transforms exponential recursion into linear polynomial time using top-down memoization and bottom-up tables.
Learn how dynamic arrays achieve O(1) amortized insertion, contiguous memory layout, and geometric doubling strategies.
Dynamic Programming (DP) is an optimization technique that solves complex problems by breaking them down into overlapping subproblems and caching intermediate solutions so each subproblem is solved exactly once.
Start with natural recursion and store computed results in a cache:
// Climbing Stairs: You can take 1 or 2 steps. How many ways to reach step N?
function climbStairsMemo(n, memo = new Map()) {
if (n <= 2) return n;
if (memo.has(n)) return memo.get(n); // Return cached result!
const result = climbStairsMemo(n - 1, memo) + climbStairsMemo(n - 2, memo);
memo.set(n, result);
return result;
}
console.log(climbStairsMemo(5)); // Output: 8
Eliminate recursion entirely by filling an iterative table from base cases upward:
function climbStairsTab(n) {
if (n <= 2) return n;
const dp = new Array(n + 1);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// Space Optimized: O(1) Space!
function climbStairsOptimized(n) {
if (n <= 2) return n;
let prev2 = 1, prev1 = 2;
for (let i = 3; i <= n; i++) {
const current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}
| Strategy | Approach | Call Stack Space | Execution Speed |
|---|---|---|---|
| Top-Down (Memoization) | Recursive | $O(N)$ call stack | Easy to formulate from intuition |
| Bottom-Up (Tabulation) | Iterative | $O(1)$ / $O(N)$ | Fastest, zero call stack overhead |
Identify state transitions and base cases. Next, let's explore the 0/1 Knapsack Pattern!