DP Steps 2 & 3: State Definition & Recurrence Relations 🎯
The two hardest steps in DP! Here are proven techniques to master them.
🏗️ Step 2: Defining the State
The Golden Rules:
Rule 1: Include ALL Information Needed for Decision Making
❌ Bad: dp[i] = "something about position i"
✅ Good: dp[i] = "maximum profit ending at day i"
Rule 2: Be Specific About What Each Index Represents
❌ Bad: dp[i][j] = "something with two strings"
✅ Good: dp[i][j] = "LCS of s1[0...i-1] and s2[0...j-1]"
Rule 3: State Should Make Subproblems Independent
Each state should contain enough info so you don't need to look "sideways"
🎨 State Definition Patterns:
Pattern 1: Position-Based (1D)
// dp[i] = best result considering elements 0 to i
// Examples: House Robber, Climbing Stairs, Max Subarray
// House Robber: dp[i] = max money robbing houses 0 to i
dp[i] = Math.max(
dp[i - 1], // don't rob house i
dp[i - 2] + nums[i] // rob house i
);Pattern 2: Substring/Subarray (2D)
// dp[i][j] = result for substring/subarray from i to j
// Examples: Palindromic Substrings, Matrix Chain Multiplication
// Palindromic Substrings: dp[i][j] = is s[i...j] palindrome?
dp[i][j] = s[i] === s[j] && (j - i <= 2 || dp[i + 1][j - 1]);Pattern 3: Two Sequences (2D)
// dp[i][j] = result comparing sequences up to position i and j
// Examples: LCS, Edit Distance, Interleaving String
// LCS: dp[i][j] = length of LCS of s1[0...i-1] and s2[0...j-1]
if (s1[i - 1] === s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}Pattern 4: State Machine
// dp[i][state] = best result at position i in given state
// Examples: Stock problems, Paint House
// Stock with cooldown: dp[i][state] where state = held|sold|rest
dp[i][held] = Math.max(dp[i - 1][held], dp[i - 1][rest] - prices[i]);
dp[i][sold] = dp[i - 1][held] + prices[i];
dp[i][rest] = Math.max(dp[i - 1][sold], dp[i - 1][rest]);🧠 State Definition Techniques:
Technique 1: "What Do I Need to Know?"
Ask yourself: "At each step, what information do I need to make the optimal decision?"
Example - Interleaving String:
- Position in s1? ✅ Need this
- Position in s2? ✅ Need this
- Position in s3? ✅ Derivable from above (i + j)
- Previous character? ❌ Not needed
State: dp[i][j] = can form s3[0...i+j-1] using s1[0...i-1] and s2[0...j-1]
Technique 2: "What Changes Between Subproblems?"
Identify what varies between similar subproblems.
Example - Edit Distance:
- String lengths change → need i, j indices
- Operations available → no extra state needed State:
dp[i][j]= min operations to transform s1[0...i-1] to s2[0...j-1]
Technique 3: "Work Backwards from Answer"
Start with what you want to return, then figure out what you need.
Example - House Robber:
- Want: maximum money from all houses
- Need: maximum money from houses 0 to i State:
dp[i]= max money from houses 0 to i
⚡ Step 3: Finding Recurrence Relations
The Golden Rules:
Rule 1: Enumerate ALL Possible Choices
At each state, what are ALL the decisions you can make?
Rule 2: Express Current State in Terms of Previous States
Current state = function of (previous states + current choice)
Rule 3: Take the Optimal Choice
Use min/max for optimization, sum for counting
🎯 Recurrence Relation Techniques:
Technique 1: "Choice Enumeration"
List every possible action at current state.
Example - Coin Change:
// dp[i] = min coins to make amount i
// Choices: use coin of value coins[j] for each j
dp[i] = Math.min(
dp[i - coins[0]] + 1, // use coin 0
dp[i - coins[1]] + 1, // use coin 1
...(dp[i - coins[k]] + 1) // use coin k
);
// Generalized:
for (let coin of coins) {
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}Technique 2: "Match or Skip" (String Problems)
For two strings, you typically have: match characters or skip from one/both.
Example - Longest Common Subsequence:
// dp[i][j] = LCS length of s1[0...i-1] and s2[0...j-1]
if (s1[i - 1] === s2[j - 1]) {
// Characters match - include in LCS
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// Characters don't match - skip from either string
dp[i][j] = Math.max(
dp[i - 1][j], // skip from s1
dp[i][j - 1] // skip from s2
);
}Technique 3: "Take or Leave" (Subset Problems)
For array elements, often you can include current element or skip it.
Example - House Robber:
// dp[i] = max money from houses 0 to i
dp[i] = Math.max(
dp[i - 1], // don't rob house i (leave)
dp[i - 2] + nums[i] // rob house i (take)
);Technique 4: "Transition Between States" (State Machine)
Define how to move between different states.
Example - Stock with Transaction Limit:
// dp[i][k][0] = max profit on day i, with k transactions, holding 0 stocks
// dp[i][k][1] = max profit on day i, with k transactions, holding 1 stock
dp[i][k][0] = Math.max(
dp[i - 1][k][0], // rest (no change)
dp[i - 1][k][1] + prices[i] // sell (1→0, profit)
);
dp[i][k][1] = Math.max(
dp[i - 1][k][1], // rest (no change)
dp[i - 1][k - 1][0] - prices[i] // buy (0→1, cost, use transaction)
);🔍 Step-by-Step Analysis Process:
For the Interleaving String Problem:
Step 2 - State Definition Process:
What do I need to know?
- How much of s1 I've used: index i
- How much of s2 I've used: index j
- How much of s3 I've formed: i + j (derivable)
What does dp[i][j] represent?
dp[i][j]= "Can I form s3[0...i+j-1] using exactly s1[0...i-1] and s2[0...j-1]?"- Very specific! Not just "something about position i,j"
Step 3 - Recurrence Relation Process:
What choices do I have at dp[i][j]?
- Take next character from s1 (if available and matches s3[i+j-1])
- Take next character from s2 (if available and matches s3[i+j-1])
Express in terms of previous states:
typescriptdp[i][j] = (s1[i - 1] === s3[i + j - 1] && dp[i - 1][j]) || // came from s1 (s2[j - 1] === s3[i + j - 1] && dp[i][j - 1]); // came from s2
🎪 Common Recurrence Patterns:
Pattern 1: Linear Combination
dp[i] = a * dp[i-1] + b * dp[i-2] + c * dp[i-3] + ...
// Examples: Fibonacci, Climbing Stairs, TribonacciPattern 2: Optimization Choice
dp[i] = Math.min/max(choice1, choice2, choice3, ...)
// Examples: Coin Change, House Robber, Jump GamePattern 3: Boolean Logic
dp[i] = (condition1 && dp[state1]) || (condition2 && dp[state2]);
// Examples: Word Break, Interleaving StringPattern 4: Accumulation
dp[i] = dp[i - 1] + (condition ? value : 0);
// Examples: Counting problems, Path counting🚨 Common Mistakes & How to Avoid:
Mistake 1: State Doesn't Capture All Info
❌ Problem: Need to look at multiple previous states ✅ Solution: Include more information in state definition
Mistake 2: Circular Dependencies
❌ Problem: dp[i] depends on dp[i] or future states ✅ Solution: Ensure you only depend on "smaller" subproblems
Mistake 3: Forgetting Edge Cases in Recurrence
❌ Problem: Array bounds, empty strings, etc. ✅ Solution: Handle boundary conditions explicitly
💡 Pro Tips:
- Start Small: Work out 2-3 examples by hand first
- Draw Pictures: Visualize the state space
- Write Recursive Solution First: Then add memoization
- Verify with Base Cases: Make sure recurrence works for simplest cases
- Think "What Would I Do Manually?": Often mirrors the recurrence
📝 Practice Exercise:
Try defining state and recurrence for these problems:
- Unique Paths: Robot in grid, can only move right/down
- Word Break: Can string be segmented into dictionary words?
- Palindrome Partitioning: Min cuts to make all substrings palindromes
Remember: These skills come with practice! Start with easier problems and gradually work your way up. The patterns will become second nature. 🚀