Quick Overview
Plain recursion recomputes `F(3)` on both branches of `F(5)`. Storing each result once collapses an exponential call tree into a linear pass: memoisation top-down, tabulation bottom-up.
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.
F(5) = F(4) + F(3), where F(3) is computed in both branches).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!
Big O describes how work grows, not how long it takes. An `O(N^2)` algorithm on 1,000 items does a million steps; the same work at `O(N log N)` does about 10,000.
A dynamic array doubles rather than growing by one, so the expensive copy lands on the 1st, 2nd, 4th and 8th push. Averaged across every push, that is `O(1)` amortised.
A nested loop tests every pair in `O(N^2)`. On sorted data, moving two pointers inward from both ends rules out half the remaining candidates at each step and settles it in one `O(N)` pass.