Pattern family: Optimization / Counting / Decision Making Difficulty range: Easy β Hard Language: Java Problems file: 02_dp_problems.md Pattern Guide Level 1: 03_dp_patterns_level1.md Pattern Guide Level 2: 04_dp_patterns_level2.md
- How to identify DP β start here
- What is DP? β Core concepts
- Optimal Substructure & Overlapping Subproblems
- Top-Down vs Bottom-Up
- Core rules
- 2-Question framework
- All 14 DP patterns
- Universal Java templates
- Quick reference cheatsheet
- Common mistakes
- Complexity summary
| If you see this... | DP Pattern |
|---|---|
"minimum / maximum cost / path to reach target" |
Min/Max Path to Target |
"number of distinct ways to reach" |
Distinct Ways (Counting) |
"can we partition / achieve sum" (boolean) |
0/1 Knapsack |
"select items, each used once, max value" |
0/1 Knapsack (Bounded) |
"items reusable / infinite supply" |
Unbounded Knapsack |
"two strings β edit / match / delete / common" |
DP on Strings (LCS family) |
"longest / shortest subsequence" |
LCS / LIS / Palindrome DP |
"grid β move right/down only" |
DP on Grids |
"burst / split / merge intervals optimally" |
Interval DP / MCM / Partition DP |
"buy and sell stocks with constraints" |
DP on Stocks |
"choose or skip current element" |
Decision Making / Fibonacci DP |
"probability / expected value over steps" |
Probability DP |
"count numbers with digit property in range" |
Digit DP |
"assign tasks, small n β€ 20" |
DP + Bitmask |
"tree node selection β no adjacent" |
DP on Trees |
Brute force = try all combinations β exponential O(2^n) or O(n!)
β If same sub-problem appears MULTIPLE TIMES in recursion tree β DP (memoize it)
β If optimal answer built from optimal sub-answers β DP (optimal substructure)
Two REQUIRED properties for DP:
1. Overlapping Sub-problems
2. Optimal Substructure
Can problem be broken into sub-problems?
β
βββ Do sub-problems OVERLAP?
β βββ YES β Use DP
β βββ NO β Divide & Conquer (no cache benefit)
β
βββ What are you computing?
β βββ Min / Max value β dp[i] = min/max(dp[prev]) + cost
β βββ Count ways β dp[i] += dp[i - way]
β βββ Boolean (can we?) β dp[i] |= dp[i - val]
β
βββ State dimension?
βββ 1D dp[i] β Fibonacci, Knapsack, LIS
βββ 2D dp[i][j] β Strings, Grid, Knapsack(item,cap)
βββ Interval dp[i][j] β MCM, Burst Balloons
βββ dp[mask] β Bitmask DP
DP solves problems by breaking them into overlapping sub-problems, solving each exactly once, and storing results to avoid recomputation.
Without DP β Fibonacci recursion tree (O(2^n)):
f(5)
/ \
f(4) f(3)
/ \ / \
f(3) f(2) f(2) f(1) β f(3), f(2) computed AGAIN!
...
With DP β memoize: f(3) and f(2) computed ONCE β O(n)
Key insight: DP is NOT about a specific algorithm β it is a technique to eliminate redundant computation by remembering answers to sub-problems.
The optimal solution to the problem contains optimal solutions to its sub-problems.
Example β Shortest Path AβC via B:
If AβBβC is optimal, then AβB must be optimal sub-path.
(If there was a shorter AβB', the overall path would use that.)
Example β Coin Change (amount=11, coins=[1,5,6]):
Optimal for 11 = 1 + Optimal for 10
= 1 + (Optimal for 5 + Optimal for 5)
Counter-example (no optimal substructure): Longest path in graph with cycles β can't decompose.
Same sub-problem solved multiple times in naive recursion.
// Fibonacci β f(3) called twice in computing f(5)
// Without memoization:
f(5) calls f(4) and f(3)
f(4) calls f(3) and f(2) β f(3) called again!
// With memoization:
int[] memo = new int[n+1];
Arrays.fill(memo, -1);
int fib(int n) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n]; // already computed!
return memo[n] = fib(n-1) + fib(n-2);
}Write the recursive solution first, then add a cache.
// Step 1: write recursion
int solve(int n) {
if (n == 0) return 0; // base case
return solve(n-1) + solve(n-2); // recurrence
}
// Step 2: add memo array
int[] memo = new int[n+1];
Arrays.fill(memo, -1);
int solve(int n) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n]; // cache hit
return memo[n] = solve(n-1) + solve(n-2);
}Pros: Easy to write, only computes needed states, natural recursion structure. Cons: Recursion stack overhead, can cause StackOverflow for large n.
Build the answer iteratively from base cases upward.
int[] dp = new int[n+1];
dp[0] = 0; dp[1] = 1;
for (int i = 2; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];Pros: No recursion stack, easier space optimization, faster in practice. Cons: Must compute all states even if not needed.
| Situation | Prefer |
|---|---|
| Not all states needed (sparse) | Top-Down |
| Space optimization critical | Bottom-Up |
| Recursion depth very large | Bottom-Up |
| Problem natural in recursion | Top-Down |
| Interview / competitive | Bottom-Up (cleaner) |
Rule 1 β Define the state BEFORE writing any code
// Always answer: "dp[i] = ???" first
// dp[i] = minimum coins to make amount i
// dp[i][j] = length of LCS of s1[0..i-1] and s2[0..j-1]
// dp[i][w] = max value using items 0..i with capacity w
// dp[i][j] = max coins from bursting balloons in open range (i..j)Rule 2 β Recurrence is the heart β get it right
// Min/Max path: dp[i] = min(dp[i-1], dp[i-2]) + cost[i]
// Count ways: dp[i] += dp[i - way[j]] for each valid way
// 0/1 Knapsack: dp[w] = max(dp[w], dp[w-wt] + val) β iterate w REVERSE
// Unbounded: dp[w] = max(dp[w], dp[w-wt] + val) β iterate w FORWARD
// LCS match: dp[i][j] = dp[i-1][j-1] + 1
// LCS no match: dp[i][j] = max(dp[i-1][j], dp[i][j-1])
// Interval: dp[i][j] = opt_k(dp[i][k] + dp[k+1][j] + cost(i,k,j))Rule 3 β Base cases β think carefully
// dp[0] = 0 β "0 cost to do nothing" β Min/Max problems
// dp[0] = 1 β "1 way to do nothing" β Counting problems
// dp[i][i] = single element value β Interval DP
// dp[0][j] = j, dp[i][0] = i β Edit Distance (all inserts/deletes)Rule 4 β 0/1 vs Unbounded: the loop direction
// 0/1 Knapsack β each item AT MOST ONCE β traverse capacity REVERSE
for (int i = 0; i < n; i++)
for (int w = W; w >= weight[i]; w--) // β REVERSE
dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);
// Unbounded Knapsack β reuse allowed β traverse capacity FORWARD
for (int i = 0; i < n; i++)
for (int w = weight[i]; w <= W; w++) // β FORWARD
dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);Rule 5 β Space optimization when dp[i] only needs dp[i-1]
// 2D β 1D rolling array
int prev2 = base0, prev1 = base1;
for (int i = 2; i <= n; i++) {
int curr = recurrence(prev1, prev2);
prev2 = prev1;
prev1 = curr;
}
return prev1;Rule 6 β Interval DP loop order: by LENGTH, not by i
// WRONG β dp[i][j] might not have sub-intervals computed yet
for (int i = 0; i < n; i++) for (int j = i; j < n; j++) ...
// CORRECT β process smaller intervals first
for (int len = 2; len <= n; len++)
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
for (int k = i; k < j; k++) ...
}| State | Meaning | Used in |
|---|---|---|
dp[i] |
Best answer for first i elements | Fibonacci, Knapsack, Decode Ways |
dp[i][j] |
Best for interval s[i..j] | Interval DP, Palindrome |
dp[i][j] |
Best matching s1[0..i] with s2[0..j] | LCS, Edit Distance |
dp[i][w] |
Best with i items, capacity w | Classic Knapsack |
dp[day][hold] |
Max profit on day, holding/not | Stock DP |
dp[mask] |
Best for subset in bitmask | Bitmask DP |
dp[node][0/1] |
Best at node, excluded/included | Tree DP |
dp[pos][tight] |
Count numbers at position, constrained | Digit DP |
| Pattern | Recurrence |
|---|---|
| Fibonacci | dp[i] = dp[i-1] + dp[i-2] |
| Min/Max path | dp[i] = min/max(dp[i-k]) + cost[i] |
| Count ways | dp[i] += dp[i - way[j]] |
| 0/1 Knapsack | dp[w] = max(dp[w], dp[w-wt]+val) β reverse w |
| Unbounded | dp[w] = max(dp[w], dp[w-wt]+val) β forward w |
| LCS | match: dp[i-1][j-1]+1 else max(dp[i-1][j], dp[i][j-1]) |
| LIS | dp[i] = max(dp[j]+1) for all j<i where arr[j]<arr[i] |
| Interval | dp[i][j] = opt over k(dp[i][k]+dp[k+1][j]+cost) |
| Stock | hold=max(hold, notHold-price); notHold=max(notHold, hold+price) |
| Tree | include[u]=val+Ξ£exclude[v]; exclude[u]=Ξ£max(include[v],exclude[v]) |
What: At each step, choose to use or skip the current element. f(n) = f(n-1) + f(n-2) style.
Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])
Java template:
int prev2 = 0, prev1 = nums[0];
for (int i = 1; i < n; i++) {
int curr = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
}
return prev1;Problems: Climbing Stairs, House Robber, Min Cost Climbing Stairs, Maximum Subarray
What: Given a target, find min/max cost path to reach it. Try all ways to arrive at each state.
Recurrence: dp[i] = min(dp[i - way[j]]) + cost
Java template:
int[] dp = new int[target + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= target; i++)
for (int way : ways)
if (way <= i && dp[i - way] != Integer.MAX_VALUE)
dp[i] = Math.min(dp[i], dp[i - way] + cost);
return dp[target] == Integer.MAX_VALUE ? -1 : dp[target];Problems: Coin Change, Min Cost Climbing Stairs, Min Path Sum, Min Falling Path Sum, Triangle, Perfect Squares
What: Count how many distinct ways to reach a target. Sum all valid prior states.
Recurrence: dp[i] += dp[i - way[j]]
Java template:
int[] dp = new int[target + 1];
dp[0] = 1; // 1 way to do nothing
for (int i = 1; i <= target; i++)
for (int way : ways)
if (way <= i)
dp[i] += dp[i - way];
return dp[target];Problems: Climbing Stairs, Unique Paths, Decode Ways, Coin Change II, Number of Dice Rolls with Target Sum
What: Each item used AT MOST ONCE. Decide: take or skip each item.
Recurrence: dp[w] = max(dp[w], dp[w - wt[i]] + val[i]) β iterate w REVERSE
Java template:
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++)
for (int w = W; w >= weight[i]; w--) // β REVERSE = 0/1 (no reuse)
dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);
return dp[W];Boolean variant (subset sum):
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums)
for (int w = target; w >= num; w--)
dp[w] = dp[w] || dp[w - num];Similar problems: Subset Sum, Partition Equal Subset Sum, Target Sum, Last Stone Weight II, Ones and Zeroes
What: Each item can be used ANY number of times.
Recurrence: dp[w] = max(dp[w], dp[w - wt[i]] + val[i]) β iterate w FORWARD
Java template:
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++)
for (int w = weight[i]; w <= W; w++) // β FORWARD = reuse allowed
dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);
return dp[W];Problems: Coin Change, Coin Change II, Perfect Squares, Rod Cutting, Combination Sum IV
What: Find longest strictly increasing subsequence. dp[i] = LIS length ending at index i.
Recurrence: dp[i] = max(dp[j] + 1) for all j < i where arr[j] < arr[i]
Java template (O(nΒ²)):
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (arr[j] < arr[i])
dp[i] = Math.max(dp[i], dp[j] + 1);
return Arrays.stream(dp).max().getAsInt();O(n log n) β Binary search on tails array:
int[] tails = new int[n]; int size = 0;
for (int num : arr) {
int lo = 0, hi = size;
while (lo < hi) { int mid=(lo+hi)/2; if(tails[mid]<num) lo=mid+1; else hi=mid; }
tails[lo] = num;
if (lo == size) size++;
}
return size;Problems: LIS, Number of LIS, Russian Doll Envelopes, Longest Chain
What: Two strings s1, s2. dp[i][j] = answer for s1[0..i-1] and s2[0..j-1].
Recurrence:
if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 (or some formula)
else: dp[i][j] = combine(dp[i-1][j], dp[i][j-1])
Java template:
int[][] dp = new int[n + 1][m + 1];
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] = dp[i-1][j-1] + 1; // LCS: match
else
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); // LCS: skip
return dp[n][m];One-string variant (palindrome):
for (int len = 2; len <= n; len++)
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
if (s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i+1][j-1] + 2;
else dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}Problems: LCS, Edit Distance, Longest Palindromic Subsequence, Distinct Subsequences, Shortest Common Supersequence, Wildcard Matching
What: Grid traversal, typically only right/down moves. dp[i][j] = answer at cell (i,j).
Recurrence: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
Java template:
int[][] dp = new int[m][n];
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]);
return dp[m-1][n-1];Problems: Unique Paths, Unique Paths II, Min Path Sum, Min Falling Path Sum, Triangle, Dungeon Game, Maximal Square, Cherry Pickup
What: Optimal way to split an interval [i..j]. Try every split point k.
Recurrence: dp[i][j] = optimal over k of (dp[i][k] + dp[k+1][j] + cost(i,k,j))
Java template:
int[][] dp = new int[n][n];
// base: dp[i][i] = single element value
for (int len = 2; len <= n; len++)
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MAX_VALUE;
for (int k = i; k < j; k++)
dp[i][j] = Math.min(dp[i][j],
dp[i][k] + dp[k+1][j] + cost(i, k, j));
}
return dp[0][n-1];Problems: Matrix Chain Multiplication, Burst Balloons, Remove Boxes, Merge Stones, Min Cost to Cut Stick, Strange Printer, Palindrome Partitioning II, Partition Array for Max Sum
What: State machine β at each day, you are either holding or not holding a stock. Track states across days.
Recurrence:
hold = max(hold, notHold - price); // keep holding OR buy today
notHold = max(notHold, hold + price); // keep not holding OR sell todayJava template:
int hold = Integer.MIN_VALUE, notHold = 0;
for (int price : prices) {
int prevHold = hold, prevNotHold = notHold;
hold = Math.max(prevHold, prevNotHold - price);
notHold = Math.max(prevNotHold, prevHold + price);
}
return notHold;Problems: Best Time to Buy/Sell Stock I, II, III, IV, With Cooldown, With Transaction Fee
What: Post-order DFS. Compute dp[node] from children's dp values.
Two states β include or exclude current node:
int[] include = new int[n];
int[] exclude = new int[n];
void dfs(int u, int parent) {
include[u] = value[u];
exclude[u] = 0;
for (int v : adj[u]) {
if (v == parent) continue;
dfs(v, u);
include[u] += exclude[v]; // can't include adjacent
exclude[u] += Math.max(include[v], exclude[v]); // take best of child
}
}
// answer = max(include[root], exclude[root])Problems: House Robber III, Binary Tree Maximum Path Sum, Diameter of Binary Tree, Unique BST II, Max Independent Set in Tree
What: Count integers in range [0..N] satisfying a digit-based property.
State: dp[pos][count][tight]
pos= current digit position (left to right)count= current count of "interesting" digittight= are we still bounded by N's digits? (0=free, 1=tight)
Java template:
int[] digits = new int[maxLen]; // store digits of N
int[][][] memo = new int[maxLen][maxCount][2];
int solve(int pos, int cnt, int tight) {
if (pos == digits.length) return cnt == target ? 1 : 0;
if (memo[pos][cnt][tight] != -1) return memo[pos][cnt][tight];
int limit = tight == 1 ? digits[pos] : 9;
int res = 0;
for (int d = 0; d <= limit; d++) {
int newTight = (tight == 1 && d == limit) ? 1 : 0;
int newCnt = cnt + (d == interestingDigit ? 1 : 0);
res += solve(pos + 1, newCnt, newTight);
}
return memo[pos][cnt][tight] = res;
}Problems: Count Numbers with Unique Digits, Digit One Count, Non-negative Integers without Consecutive Ones
What: State includes a bitmask representing a subset of elements. Used when n β€ 20.
Recurrence: dp[mask | (1 << next)][next] = min(dp[mask][curr] + cost[curr][next])
Java template:
int n = elements.length;
int[][] dp = new int[1 << n][n];
for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE);
// Initialize starting states
for (int i = 0; i < n; i++) dp[1 << i][i] = 0;
for (int mask = 1; mask < (1 << n); mask++)
for (int u = 0; u < n; u++) {
if ((mask >> u & 1) == 0 || dp[mask][u] == Integer.MAX_VALUE) continue;
for (int v = 0; v < n; v++) {
if ((mask >> v & 1) == 1) continue; // already visited
int newMask = mask | (1 << v);
dp[newMask][v] = Math.min(dp[newMask][v], dp[mask][u] + cost[u][v]);
}
}Problems: Shortest Superstring, Number of Ways to Wear Hats, Maximum Students Taking Exam, TSP, Task Allotment
What: dp[state] = probability or expected value at that state. Propagate probabilities forward or backward.
Recurrence: dp[state] = Ξ£ (probability_of_transition Γ dp[next_state])
Java template:
double[][] dp = new double[n][n]; // state = position (r, c)
dp[startR][startC] = 1.0;
for (int step = 0; step < k; step++) {
double[][] next = new double[n][n];
for (int r = 0; r < n; r++)
for (int c = 0; c < n; c++) {
if (dp[r][c] == 0) continue;
for (int[] move : moves) {
int nr = r + move[0], nc = c + move[1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n)
next[nr][nc] += dp[r][c] / moves.length;
}
}
dp = next;
}Problems: Knight Probability in Chessboard, Soup Servings, New 21 Game, Dice Roll Simulation, Two Boxes
// βββ TEMPLATE A: Fibonacci / 1D Rolling ββββββββββββββββββββββββββββββββββββββ
int prev2 = base0, prev1 = base1;
for (int i = 2; i <= n; i++) {
int curr = Math.max(prev1, prev2 + cost[i]); // or + / min
prev2 = prev1; prev1 = curr;
}
return prev1;
// βββ TEMPLATE B: Min/Max Path to Target ββββββββββββββββββββββββββββββββββββββ
int[] dp = new int[target + 1];
Arrays.fill(dp, Integer.MAX_VALUE); dp[0] = 0;
for (int i = 1; i <= target; i++)
for (int way : ways)
if (way <= i && dp[i-way] != Integer.MAX_VALUE)
dp[i] = Math.min(dp[i], dp[i-way] + 1);
return dp[target] > target ? -1 : dp[target];
// βββ TEMPLATE C: Counting (Distinct Ways) ββββββββββββββββββββββββββββββββββββ
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 1; i <= target; i++)
for (int way : ways)
if (way <= i) dp[i] += dp[i-way];
return dp[target];
// βββ TEMPLATE D: 0/1 Knapsack (1D, REVERSE) ββββββββββββββββββββββββββββββββββ
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++)
for (int w = W; w >= weight[i]; w--)
dp[w] = Math.max(dp[w], dp[w-weight[i]] + value[i]);
return dp[W];
// βββ TEMPLATE E: Unbounded Knapsack (1D, FORWARD) ββββββββββββββββββββββββββββ
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++)
for (int w = weight[i]; w <= W; w++)
dp[w] = Math.max(dp[w], dp[w-weight[i]] + value[i]);
return dp[W];
// βββ TEMPLATE F: LIS O(nΒ²) βββββββββββββββββββββββββββββββββββββββββββββββββββ
int[] dp = new int[n]; Arrays.fill(dp, 1);
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (arr[j] < arr[i]) dp[i] = Math.max(dp[i], dp[j]+1);
return Arrays.stream(dp).max().getAsInt();
// βββ TEMPLATE G: LIS O(n log n) ββββββββββββββββββββββββββββββββββββββββββββββ
int[] tails = new int[n]; int size = 0;
for (int num : arr) {
int lo = 0, hi = size;
while (lo < hi) { int mid=(lo+hi)/2; if(tails[mid]<num) lo=mid+1; else hi=mid; }
tails[lo] = num;
if (lo == size) size++;
}
return size;
// βββ TEMPLATE H: DP on Two Strings βββββββββββββββββββββββββββββββββββββββββββ
int[][] dp = new int[n+1][m+1];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
dp[i][j] = s1.charAt(i-1)==s2.charAt(j-1)
? dp[i-1][j-1] + 1
: Math.max(dp[i-1][j], dp[i][j-1]);
return dp[n][m];
// βββ TEMPLATE I: DP on Grids βββββββββββββββββββββββββββββββββββββββββββββββββ
int[][] dp = new int[m][n]; 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]);
return dp[m-1][n-1];
// βββ TEMPLATE J: Interval DP βββββββββββββββββββββββββββββββββββββββββββββββββ
int[][] dp = new int[n][n];
for (int len=2;len<=n;len++)
for (int i=0;i<=n-len;i++) {
int j=i+len-1; dp[i][j]=Integer.MAX_VALUE;
for (int k=i;k<j;k++)
dp[i][j]=Math.min(dp[i][j], dp[i][k]+dp[k+1][j]+cost(i,k,j));
}
return dp[0][n-1];
// βββ TEMPLATE K: Stock DP ββββββββββββββββββββββββββββββββββββββββββββββββββββ
int hold=Integer.MIN_VALUE, notHold=0;
for (int price:prices) {
int ph=hold, pn=notHold;
hold=Math.max(ph, pn-price);
notHold=Math.max(pn, ph+price);
}
return notHold;
// βββ TEMPLATE L: Tree DP βββββββββββββββββββββββββββββββββββββββββββββββββββββ
int[] inc=new int[n], exc=new int[n];
void dfs(int u, int p) {
inc[u]=val[u]; exc[u]=0;
for (int v:adj[u]) { if(v==p) continue; dfs(v,u);
inc[u]+=exc[v]; exc[u]+=Math.max(inc[v],exc[v]); }
}
// βββ TEMPLATE M: Memoization (Top-Down) ββββββββββββββββββββββββββββββββββββββ
int[] memo = new int[n+1]; Arrays.fill(memo,-1);
int solve(int i) {
if (i<=0) return 0;
if (memo[i]!=-1) return memo[i];
return memo[i] = Math.min(solve(i-1)+cost1, solve(i-2)+cost2);
}SIGNAL β PATTERN β CRITICAL CODE
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
"min/max cost to reach target" β Min/Max Path β dp[i]=min(dp[i-way])+cost
"count distinct ways" β Count Ways β dp[i]+=dp[i-way]; dp[0]=1
"select items, each once" β 0/1 Knapsack β reverse capacity
"select items, reuse allowed" β Unbounded β forward capacity
"partition into equal subsets" β 0/1 Knapsack(bool) β dp[w]|=dp[w-num]
"two strings, longest common" β LCS β match:dp[i-1][j-1]+1
"two strings, min edit operations" β Edit Distance β replace/insert/delete
"longest increasing subsequence" β LIS β dp[i]=max(dp[j]+1)
"grid, top-left to bottom-right" β Grid DP β dp[i][j]=+min(up,left)
"split interval, try all split points" β Interval DP β len loop, k split loop
"buy sell stocks with restrictions" β Stock DP β hold/notHold machine
"tree nodes, no two adjacent" β Tree DP β include/exclude DFS
"count numbers with digit property" β Digit DP β pos/tight/cnt states
"assign tasks, nβ€20" β Bitmask DP β dp[mask|(1<<j)]
"probability / expected steps" β Probability DP β dp=Ξ£ PΒ·next
3-step approach (do this before writing code):
Step 1 β DEFINE: dp[i] = ??? (exact meaning)
Step 2 β RECURRENCE: dp[i] = f(...) (the equation)
Step 3 β BASE CASE: dp[0] = ? (empty/zero state)
0/1 vs Unbounded β the one thing to remember:
0/1 (each item once) β reverse: for w = W..weight[i]
Unbounded (reuse) β forward: for w = weight[i]..W
// BUG β dp[0]=0 means 0 ways to do nothing β no ways propagate
int[] dp = new int[target+1]; dp[0] = 0; // WRONG for counting
// FIX β dp[0]=1 means 1 way to do nothing (the empty selection)
int[] dp = new int[target+1]; dp[0] = 1; // CORRECT for counting// BUG β item used multiple times
for (int w = weight[i]; w <= W; w++) dp[w] = max(...);
// FIX β reverse prevents reuse
for (int w = W; w >= weight[i]; w--) dp[w] = max(...);// BUG β MAX_VALUE + 1 wraps negative
dp[i] = Math.min(dp[i], dp[i-way] + 1); // if dp[i-way]=MAX_VALUE β overflow
// FIX β guard before adding
if (dp[i-way] != Integer.MAX_VALUE)
dp[i] = Math.min(dp[i], dp[i-way] + 1);// BUG β sub-intervals may not be ready
for (int i=0;i<n;i++) for (int j=i+1;j<n;j++) ...
// FIX β iterate by length (ensures sub-intervals computed first)
for (int len=2;len<=n;len++)
for (int i=0;i<=n-len;i++) { int j=i+len-1; ... }// BUG β uses new hold to compute notHold
hold = Math.max(hold, notHold - price);
notHold = Math.max(notHold, hold + price); // hold is already updated!
// FIX β save previous values
int ph=hold, pn=notHold;
hold = Math.max(ph, pn - price);
notHold = Math.max(pn, ph + price);// BUG β integer overflow for large inputs
dp[i] += dp[i-way];
// FIX
dp[i] = (dp[i] + dp[i-way]) % MOD;// BUG β forgetting to initialize first row/column
int[][] dp = new int[n+1][m+1]; // all zeros β wrong!
// FIX β first row = insert all of s2; first col = delete all of s1
for (int i=0;i<=n;i++) dp[i][0]=i;
for (int j=0;j<=m;j++) dp[0][j]=j;| Pattern | Time | Space | Optimized Space |
|---|---|---|---|
| Fibonacci / 1D | O(n) | O(n) | O(1) β two variables |
| Min/Max Path | O(n Γ ways) | O(n) | O(n) |
| Distinct Ways | O(n Γ ways) | O(n) | O(n) |
| 0/1 Knapsack | O(n Γ W) | O(n Γ W) | O(W) β 1D rolling |
| Unbounded Knapsack | O(n Γ W) | O(W) | Already O(W) |
| LIS β O(nΒ²) | O(nΒ²) | O(n) | β |
| LIS β O(n log n) | O(n log n) | O(n) | β |
| DP on Strings | O(n Γ m) | O(n Γ m) | O(min(n,m)) β 1 row |
| DP on Grids | O(m Γ n) | O(m Γ n) | O(n) β single row |
| Interval DP | O(nΒ³) | O(nΒ²) | β |
| DP on Stocks | O(n) | O(1) | Already O(1) |
| DP on Trees | O(n) | O(n) | β |
| Digit DP | O(digits Γ states) | O(digits Γ states) | β |
| Bitmask DP | O(2^n Γ n) | O(2^n Γ n) | β only viable n β€ 20 |
| Probability DP | O(steps Γ states) | O(states) | β |