Grid-Based DP Problems
Grid DP problems involve finding optimal paths or counts in a 2D grid.
Unique Paths
// Count paths from top-left to bottom-right (only right/down moves)
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// Base cases: first row and first column have 1 path each
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
// Fill the grid
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]; // From top + from left
}
}
return dp[m - 1][n - 1];
}
// Space-optimized: O(n) space
public int uniquePathsOptimized(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j - 1]; // dp[j] (from top) + dp[j-1] (from left)
}
}
return dp[n - 1];
}
Minimum Path Sum
// Find path with minimum sum from top-left to bottom-right
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) continue; // Start cell
else if (i == 0) grid[i][j] += grid[i][j - 1]; // First row
else if (j == 0) grid[i][j] += grid[i - 1][j]; // First column
else grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
}
}
return grid[m - 1][n - 1];
}
Unique Paths with Obstacles
// Grid with obstacles (1 = obstacle, 0 = empty)
public int uniquePathsWithObstacles(int[][] grid) {
int m = grid.length, n = grid[0].length;
if (grid[0][0] == 1 || grid[m-1][n-1] == 1) return 0;
int[][] dp = new int[m][n];
dp[0][0] = 1;
for (int i = 1; i < m; i++) {
dp[i][0] = (grid[i][0] == 0 && dp[i-1][0] == 1) ? 1 : 0;
}
for (int j = 1; j < n; j++) {
dp[0][j] = (grid[0][j] == 0 && dp[0][j-1] == 1) ? 1 : 0;
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (grid[i][j] == 0) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
Key Pattern for Grid DP
// State: dp[i][j] = optimal value at cell (i,j)
// Transition: dp[i][j] = f(dp[i-1][j], dp[i][j-1]) // for 4-directional
// Or: dp[i][j] = f(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) // for diagonal
Space Optimization for Grid DP
// If transition only depends on previous row, use single row
int[] dp = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) dp[j] = grid[0][0];
else if (i == 0) dp[j] = dp[j-1] + grid[0][j];
else if (j == 0) dp[j] = dp[j] + grid[i][0];
else dp[j] = Math.min(dp[j], dp[j-1]) + grid[i][j];
}
}
return dp[n-1];
String DP Problems
String DP problems involve comparing or transforming two strings.
Longest Common Subsequence (LCS)
// Find length of longest common subsequence of two strings
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
// dp[i][j] = LCS length of text1[0..i-1] and text2[0..j-1]
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1; // Characters match
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); // Skip one
}
}
}
return dp[m][n];
}
// Space-optimized: O(n) space
public int lcsOptimized(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[] dp = new int[n + 1];
for (int i = 1; i <= m; i++) {
int prev = 0; // dp[i-1][j-1]
for (int j = 1; j <= n; j++) {
int temp = dp[j];
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[j] = prev + 1;
} else {
dp[j] = Math.max(dp[j], dp[j - 1]);
}
prev = temp;
}
}
return dp[n];
}
Edit Distance
// Minimum operations to convert word1 to word2
// Operations: insert, delete, replace
public int editDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1];
// Base cases: converting empty string
for (int i = 0; i <= m; i++) dp[i][0] = i; // Delete all
for (int j = 0; j <= n; j++) dp[0][j] = j; // Insert all
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]; // No operation needed
} else {
dp[i][j] = 1 + Math.min(
dp[i - 1][j - 1], // Replace
Math.min(dp[i - 1][j], // Delete
dp[i][j - 1]) // Insert
);
}
}
}
return dp[m][n];
}
Longest Palindromic Subsequence
// Find longest palindromic subsequence in a string
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n];
// Base case: single character
for (int i = 0; i < n; i++) dp[i][i] = 1;
// Fill for lengths 2 to n
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]);
}
}
}
return dp[0][n - 1];
}
String DP Patterns
| Problem | State | Transition |
|---|---|---|
| LCS | dp[i][j] = LCS of s1[0..i-1], s2[0..j-1] | match: dp[i-1][j-1]+1, else: max(dp[i-1][j], dp[i][j-1]) |
| Edit Distance | dp[i][j] = ops to convert s1[0..i-1] to s2[0..j-1] | match: dp[i-1][j-1], else: 1+min(replace, delete, insert) |
| Palindrome | dp[i][j] = LPS in s[i..j] | match: dp[i+1][j-1]+2, else: max(dp[i+1][j], dp[i][j-1]) |
Practice Problems
Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0. A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
Example:
Input: text1 = "abcde", text2 = "ace"
Output: 3
The longest common subsequence is "ace" and its length is 3.
Edge Cases:
- One or both strings empty — answer is 0
- Both strings identical — answer is the string length
- No common characters — answer is 0
- One string is subsequence of the other — answer is the shorter string length
- Strings with all same characters
Quiz
1. In Edit Distance, what are the three operations allowed?
2. What is the state transition for LCS when characters match?
3. What is a common mistake when implementing 2D Dynamic Programming?
Flashcards
Question
What is the state transition for Edit Distance?
Click to reveal answer
Answer
If chars match: dp[i][j] = dp[i-1][j-1]. Else: dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) representing replace, delete, insert.
Question
How do you optimize 2D DP space from O(m*n) to O(n)?
Click to reveal answer
Answer
When dp[i][j] only depends on current and previous row, use a single 1D array and update carefully left-to-right or right-to-left.
Question
2D Dynamic Programming best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1. Grid DP: dp[i][j] depends on neighbors (top, left, diagonal)
- 2. String DP: dp[i][j] depends on comparing characters at positions i-1, j-1
- 3. Space optimization uses single row when only previous row needed
- 4. Always handle base cases for first row and first column
Interview Tips
- • Draw out small examples to understand state transitions
- • Clarify if subsequence (non-contiguous) or substring (contiguous)
- • Discuss space optimization after giving O(mn) solution
- • For Edit Distance, explain the three operations clearly
Cheat Sheet
2D DP Cheat Sheet
Grid DP Patterns:
- Unique paths: dp[i][j] = dp[i-1][j] + dp[i][j-1]
- Min path sum: dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
- Obstacles: set dp[i][j] = 0 when obstacle
String DP Patterns:
- LCS: match: dp[i-1][j-1]+1, else: max(dp[i-1][j], dp[i][j-1])
- Edit Distance: match: dp[i-1][j-1], else: 1+min(replace, delete, insert)
- Palindrome: match: dp[i+1][j-1]+2, else: max(dp[i+1][j], dp[i][j-1])
Space Optimization:
- Use single row when transition depends on previous row only
- Update direction matters (left-to-right vs right-to-left)
Base Cases:
- First row/column usually initialized to 1 or 0
- Empty string comparisons: dp[0][j] = j, dp[i][0] = i