Dynamic Programming (DP) is not about memorizing problems.
It is about recognizing repeated work and optimizing decisions.
At its core, DP answers one question:
โCan I break this problem into smaller overlapping subproblems and reuse their answers?โ
A state represents the minimum information needed to make future decisions.
Example:
dp[n]dp[i][j]dp[index][weight]Good DP states are:
Bad state design = impossible DP.
int fib(int n){
if(n <= 1) return n;
if(dp[n] != -1) return dp[n];
return dp[n] = fib(n-1) + fib(n-2);
}
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
Each state depends on previous one or two states.
You can climb 1 or 2 steps.
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
Time: O(n)
Space can be optimized to O(1).
Two changing variables (index + capacity, string lengths, grid).
for(int i = 0; i < m; i++)
dp[i][0] = 1;
for(int j = 0; j < n; j++)
dp[0][j] = 1;
for(int i = 1; i < m; i++)
for(int j = 1; j < n; j++)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
Is there a subset with sum = target?
dp[i][s] โ can we form sum s using first i elements?
dp[0][0] = true;
for(int i = 1; i <= n; i++){
for(int s = 0; s <= sum; s++){
dp[i][s] = dp[i-1][s];
if(s >= arr[i-1])
dp[i][s] |= dp[i-1][s - arr[i-1]];
}
}
This is the foundation of knapsack problems.
Each item can be chosen once.
dp[i][w] = max(
dp[i-1][w],
value[i] + dp[i-1][w - weight[i]]
)
Items can be chosen multiple times.
Key difference:
dp[i][w] = max(
dp[i-1][w],
value[i] + dp[i][w - weight[i]]
)
Used in:
dp[0] = 0;
for(int i = 1; i <= amount; i++){
dp[i] = INF;
for(int coin : coins){
if(i >= coin)
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
dp[0] = 1;
for(int coin : coins){
for(int i = coin; i <= amount; i++)
dp[i] += dp[i - coin];
}
Find longest sequence common to both strings.
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(s1.charAt(i-1) == s2.charAt(j-1))
dp[i][j] = 1 + dp[i-1][j-1];
else
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
Used in:
for(int i = 0; i < n; i++){
dp[i] = 1;
for(int j = 0; j < i; j++){
if(arr[j] < arr[i])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
Uses binary search + patience sorting idea.
Find minimum cost to multiply matrices.
dp[i][j] โ minimum cost to multiply matrices i to j
for(int len = 2; len <= n; len++){
for(int i = 1; i + len - 1 <= n; i++){
int j = i + len - 1;
dp[i][j] = INF;
for(int k = i; k < j; k++){
dp[i][j] = Math.min(
dp[i][j],
dp[i][k] + dp[k+1][j] + arr[i-1]*arr[k]*arr[j]
);
}
}
}
Classic partition DP.
Use postorder traversal.
int maxPath(TreeNode root){
if(root == null) return 0;
int left = Math.max(0, maxPath(root.left));
int right = Math.max(0, maxPath(root.right));
globalMax = Math.max(globalMax, root.val + left + right);
return root.val + Math.max(left, right);
}
Tree DP often returns values to parent.
dp[0][0] = grid[0][0];
for(int i = 1; i < m; i++)
dp[i][0] = dp[i-1][0] + grid[i][0];
for(int j = 1; j < n; j++)
dp[0][j] = dp[0][j-1] + grid[0][j];
for(int i = 1; i < m; i++)
for(int j = 1; j < n; j++)
dp[i][j] = grid[i][j] +
Math.min(dp[i-1][j], dp[i][j-1]);
Used when:
State:
dp[mask][i]
Meaning:
Minimum cost to visit cities in mask ending at city i.
dp[mask | (1 << j)][j] = min(
dp[mask | (1 << j)][j],
dp[mask][i] + cost[i][j]
);
Very powerful but advanced.