Use DP when subproblems overlap and exhibit optimal substructure. Replace exponential recursion with polynomial-time tabulation or memoization.
Checklist:
1) State definition: dp[i][...]= meaning
2) Base cases: dp[0][...], empty, impossible = -∞/∞
3) Transition: how to go from smaller to larger states
4) Order: topological order consistent with dependencies
5) Answer: which state(s) encode the final solution
// 0/1 knapsack: maximize value with weight limit W
function knapsack01(weights, values, W) {
const dp = Array(W + 1).fill(0);
for (let i = 0; i < weights.length; i++) {
for (let w = W; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[W];
}
// Unbounded knapsack (unlimited items)
function knapsackUnbounded(weights, values, W) {
const dp = Array(W + 1).fill(0);
for (let i = 0; i < weights.length; i++) {
for (let w = weights[i]; w <= W; w++) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[W];
}
// O(n log n) LIS length via patience sorting
function LISLength(a) {
const tails = [];
for (const x of a) {
let l = 0, r = tails.length;
while (l < r) {
const m = (l + r) >> 1;
if (tails[m] < x) l = m + 1; else r = m;
}
tails[l] = x;
}
return tails.length;
}
// Maximum independent set on a tree
function treeMIS(n, adj) {
const dp0 = Array(n).fill(0); // exclude node
const dp1 = Array(n).fill(0); // include node
function dfs(u, p) {
dp1[u] = 1;
for (const v of adj[u]) if (v !== p) {
dfs(v, u);
dp0[u] += Math.max(dp0[v], dp1[v]);
dp1[u] += dp0[v];
}
}
dfs(0, -1);
return Math.max(dp0[0], dp1[0]);
}