0% found this document useful (0 votes)
2 views12 pages

Chapter 23 Dynamic Programming Advanced in Java

Chapter 23 of the Java Data Structures & Algorithms Series focuses on advanced dynamic programming techniques, including 2D DP, Interval DP, and String DP, which are commonly encountered in FAANG interviews. It covers various patterns such as Longest Common Subsequence, Edit Distance, Longest Increasing Subsequence, and others, providing recurrence relations, memoization, tabulation methods, and time-space complexities for each problem. The chapter also includes practical Java implementations for each pattern, demonstrating how to solve these problems efficiently.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views12 pages

Chapter 23 Dynamic Programming Advanced in Java

Chapter 23 of the Java Data Structures & Algorithms Series focuses on advanced dynamic programming techniques, including 2D DP, Interval DP, and String DP, which are commonly encountered in FAANG interviews. It covers various patterns such as Longest Common Subsequence, Edit Distance, Longest Increasing Subsequence, and others, providing recurrence relations, memoization, tabulation methods, and time-space complexities for each problem. The chapter also includes practical Java implementations for each pattern, demonstrating how to solve these problems efficiently.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Chapter 23

Dynamic Programming (Advanced) in


Java
Java Data Structures & Algorithms Series

This chapter covers 2D DP, Interval DP, and String DP — the patterns that appear most frequently
in FAANG interviews and competitive programming. Every problem follows the same 4-step
pipeline from Chapter 22.

1 Pattern 1 — Longest Common Subsequence (LCS)

Problem: Find the length of the longest subsequence common to both strings. Subsequence does
not need to be contiguous.

s1 = "ABCBDAB"
s2 = "BDCABA"
LCS = "BCBA" or "BDAB" → length = 4

The Recurrence

If s1[i] == s2[j] → dp[i][j] = 1 + dp[i-1][j-1] (characters match, extend


LCS)
If s1[i] != s2[j] → dp[i][j] = max(dp[i-1][j], dp[i][j-1]) (skip one char)

// Step 2: Memoization
public static int lcsMemo(String s1, String s2, int i, int j, int[][] memo) {
if (i == 0 || j == 0) return 0;
if (memo[i][j] != -1) return memo[i][j];

if ([Link](i-1) == [Link](j-1))
return memo[i][j] = 1 + lcsMemo(s1, s2, i-1, j-1, memo);
return memo[i][j] = [Link](
lcsMemo(s1, s2, i-1, j, memo), // skip s1[i]
lcsMemo(s1, s2, i, j-1, memo) // skip s2[j]
);
}

// Step 3: Tabulation
public static int lcs(String s1, String s2) {
int m = [Link](), n = [Link]();
int[][] dp = new int[m+1][n+1];

for (int i = 1; i <= m; i++) {


for (int j = 1; j <= n; j++) {
if ([Link](i-1) == [Link](j-1))
dp[i][j] = 1 + dp[i-1][j-1];
else
dp[i][j] = [Link](dp[i-1][j], dp[i][j-1]);
}
}
return dp[m][n];
}

// Time: O(m×n) | Space: O(m×n)

Print the Actual LCS String (Backtracking)

public static String printLCS(String s1, String s2) {


int m = [Link](), n = [Link]();
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if ([Link](i-1) == [Link](j-1)) dp[i][j] = 1 + dp[i-1][j-1];
else dp[i][j] = [Link](dp[i-1][j], dp[i][j-1]);

// Backtrack to find the actual string


StringBuilder sb = new StringBuilder();
int i = m, j = n;
while (i > 0 && j > 0) {
if ([Link](i-1) == [Link](j-1)) {
[Link]([Link](i-1)); i--; j--;
} else if (dp[i-1][j] > dp[i][j-1]) i--;
else j--;
}
return [Link]().toString();
}

2 Pattern 2 — Edit Distance (Levenshtein Distance)

Problem: Minimum operations (insert, delete, replace) to convert string s1 to s2.

s1 = "horse" s2 = "ros"
horse → rorse (replace h→r)
rorse → rose (delete r)
rose → ros (delete e)
Answer: 3 operations

The Recurrence

If s1[i] == s2[j] → dp[i][j] = dp[i-1][j-1] (no operation needed)


Else → dp[i][j] = 1 + min(
dp[i-1][j], // delete from s1
dp[i][j-1], // insert into s1
dp[i-1][j-1] // replace in s1
)

public static int editDistance(String s1, String s2) {


int m = [Link](), n = [Link]();
int[][] dp = new int[m+1][n+1];

for (int i = 0; i <= m; i++) dp[i][0] = i; // delete all chars of s1


for (int j = 0; j <= n; j++) dp[0][j] = j; // insert all chars of s2

for (int i = 1; i <= m; i++) {


for (int j = 1; j <= n; j++) {
if ([Link](i-1) == [Link](j-1))
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = 1 + [Link](dp[i-1][j-1],
[Link](dp[i-1][j], dp[i][j-1]));
}
}
return dp[m][n];
}

// Time: O(m×n) | Space: O(m×n)

Space Optimized to O(n)

public static int editDistanceOptimal(String s1, String s2) {


int m = [Link](), n = [Link]();
int[] prev = new int[n+1], curr = new int[n+1];
for (int j = 0; j <= n; j++) prev[j] = j;

for (int i = 1; i <= m; i++) {


curr[0] = i;
for (int j = 1; j <= n; j++) {
if ([Link](i-1) == [Link](j-1)) curr[j] = prev[j-1];
else curr[j] = 1 + [Link](prev[j-1], [Link](prev[j], curr[j-1]));
}
int[] temp = prev; prev = curr; curr = temp;
}
return prev[n];
}

// Time: O(m×n) | Space: O(n)

3 Pattern 3 — Longest Increasing Subsequence (LIS)

Problem: Find the length of the longest strictly increasing subsequence.

arr = [10, 9, 2, 5, 3, 7, 101, 18]


LIS = [2, 3, 7, 18] OR [2, 5, 7, 101] → length = 4

Method 1 — DP O(n²)

public static int lis(int[] nums) {


int n = [Link];
int[] dp = new int[n];
[Link](dp, 1); // every element is LIS of length 1 by itself

int maxLen = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i])
dp[i] = [Link](dp[i], dp[j] + 1);
}
maxLen = [Link](maxLen, dp[i]);
}
return maxLen;
}

// Time: O(n²) | Space: O(n)

Method 2 — Binary Search O(n log n)

public static int lisOptimal(int[] nums) {


List<Integer> tails = new ArrayList<>();
// tails[i] = smallest tail element of all LIS of length i+1

for (int num : nums) {


int lo = 0, hi = [Link]();
while (lo < hi) {
int mid = (lo + hi) / 2;
if ([Link](mid) < num) lo = mid + 1;
else hi = mid;
}
if (lo == [Link]()) [Link](num); // extend LIS
else [Link](lo, num); // replace with smaller tail
}
return [Link]();
}

// Time: O(n log n) | Space: O(n)

4 Pattern 4 — Unique Paths (2D Grid DP)

Problem: Robot starts at top-left of m×n grid. Can only move right or down. Count unique paths to
bottom-right.

3×3 Grid:
S . .
. . .
. . E

Paths: 6

The Recurrence
dp[i][j] = dp[i-1][j] + dp[i][j-1] (came from top OR came from left)

// Tabulation
public static int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
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];

return dp[m-1][n-1];
}

// Space Optimized to O(n)


public static int uniquePathsOptimal(int m, int n) {
int[] dp = new int[n];
[Link](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];
}

// Time: O(m×n) | Space: O(n)

5 Pattern 5 — Minimum Path Sum

Problem: Find path from top-left to bottom-right with minimum sum of values.

Grid:
[1, 3, 1]
[1, 5, 1]
[4, 2, 1]

Min path: 1→3→1→1→1 = 7

public static int minPathSum(int[][] grid) {


int m = [Link], n = grid[0].length;
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] + [Link](dp[i-1][j], dp[i][j-1]);

return dp[m-1][n-1];
}
// Time: O(m×n) | Space: O(m×n)

6 Pattern 6 — Longest Palindromic Subsequence

Problem: Find longest palindromic subsequence in a string.

s = "bbbab"
LPS = "bbbb" → length = 4
💡 Key insight: LPS(s) = LCS(s, reverse(s))

// Using LCS shortcut


public static int longestPalindromicSubseq(String s) {
String rev = new StringBuilder(s).reverse().toString();
return lcs(s, rev); // reuse LCS from Pattern 1!
}

// OR solve directly with interval DP


public static int lpsDirect(String s) {
int n = [Link]();
int[][] dp = new int[n][n];

for (int i = 0; i < n; i++) dp[i][i] = 1; // single char = palindrome of length


1

for (int len = 2; len <= n; len++) {


for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
if ([Link](i) == [Link](j))
dp[i][j] = 2 + (len > 2 ? dp[i+1][j-1] : 0);
else
dp[i][j] = [Link](dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}

// Time: O(n²) | Space: O(n²)

7 Pattern 7 — Matrix Chain Multiplication (Interval DP)

Problem: Given matrices, find the minimum number of multiplications to compute their product.
Only parenthesization changes — not the order.

Matrices A(10×30), B(30×5), C(5×60)


(A×B)×C = 10×30×5 + 10×5×60 = 1500 + 3000 = 4500
A×(B×C) = 30×5×60 + 10×30×60 = 9000 + 18000 = 27000
Answer: 4500
public static int matrixChain(int[] dims) {
// dims[i-1] x dims[i] = dimensions of matrix i
int n = [Link] - 1;
int[][] dp = new int[n][n];
// dp[i][j] = min cost to multiply matrices i through j

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++) {


int cost = dp[i][k] + dp[k+1][j]
+ dims[i] * dims[k+1] * dims[j+1];
dp[i][j] = [Link](dp[i][j], cost);
}
}
}
return dp[0][n-1];
}

// Time: O(n³) | Space: O(n²)

8 Pattern 8 — Burst Balloons

Problem: Burst all balloons to maximize coins. Bursting balloon i gives nums[i-1] × nums[i] ×
nums[i+1].

nums = [3, 1, 5, 8]
Optimal: burst 1→3×1×5=15, burst 5→3×5×8=120, burst 3→1×3×8=24, burst 8→1×8×1=8
Total: 167

⭐ Key Insight: Think of k as the LAST balloon burst in range (i, j), not the first.
This avoids dependency on already-burst neighbours — the classic interval DP trick.

public static int maxCoins(int[] nums) {


int n = [Link];
int[] arr = new int[n + 2];
arr[0] = arr[n+1] = 1; // boundary balloons of value 1
for (int i = 0; i < n; i++) arr[i+1] = nums[i];

int[][] dp = new int[n+2][n+2];


// dp[i][j] = max coins from bursting all balloons between i and j (exclusive)

for (int len = 1; len <= n; len++) {


for (int i = 1; i <= n - len + 1; i++) {
int j = i + len - 1;
for (int k = i; k <= j; k++) {
dp[i][j] = [Link](dp[i][j],
dp[i][k-1] + arr[i-1] * arr[k] * arr[j+1] + dp[k+1][j]);
}
}
}
return dp[1][n];
}

// Time: O(n³) | Space: O(n²)

9 Pattern 9 — Partition Equal Subset Sum

Problem: Can array be partitioned into two subsets with equal sum?

nums = [1, 5, 11, 5]


Partition: [1, 5, 5] and [11] → both sum to 11 → true

public static boolean canPartition(int[] nums) {


int total = 0;
for (int n : nums) total += n;
if (total % 2 != 0) return false; // odd total → impossible

int target = total / 2;


boolean[] dp = new boolean[target + 1];
dp[0] = true; // sum 0 always achievable (empty subset)

for (int num : nums)


// Traverse right to left (0/1 knapsack — use each item once)
for (int j = target; j >= num; j--)
dp[j] = dp[j] || dp[j - num];

return dp[target];
}

// Time: O(n × target) | Space: O(target)

10 LCS Variants — All Derived from LCS

Problem Trick Formula


LCS Direct 2D DP dp[i][j]
Edit Distance Insert/Delete/Replace dp[i][j] = 1 + min(3 ops)
Shortest Common Supersequence SCS = m + n - LCS dp[m][n] = m+n-lcs
Longest Palindromic LPS = LCS(s, rev(s)) Reuse LCS
Subsequence
Count Distinct Subsequences Count ways s2 appears in s1 Modified LCS
Wildcard Matching '?' any char, '*' any seq Modified LCS

11 Full Runnable Java Program

import [Link].*;

public class Chapter23DPAdvanced {


public static void main(String[] args) {
// LCS
[Link]("LCS: " + lcs("ABCBDAB", "BDCABA")); // 4
[Link]("LCS string: " + printLCS("ABCBDAB","BDCABA")); // BCBA

// Edit Distance
[Link]("Edit Distance: " + editDistance("horse","ros")); // 3

// LIS
int[] arr = {10, 9, 2, 5, 3, 7, 101, 18};
[Link]("LIS O(n²): " + lis(arr)); // 4
[Link]("LIS O(nlogn): " + lisOptimal(arr)); // 4

// Unique Paths
[Link]("Unique Paths 3x7: " + uniquePathsOptimal(3,7)); // 28

// Min Path Sum


int[][] grid = {{1,3,1},{1,5,1},{4,2,1}};
[Link]("Min Path Sum: " + minPathSum(grid)); // 7

// Longest Palindromic Subsequence


[Link]("LPS 'bbbab': " + lpsDirect("bbbab")); // 4

// Matrix Chain
int[] dims = {10, 30, 5, 60};
[Link]("Matrix Chain: " + matrixChain(dims)); // 4500

// Burst Balloons
int[] balloons = {3, 1, 5, 8};
[Link]("Burst Balloons: " + maxCoins(balloons)); // 167

// Partition Equal Subset


int[] nums = {1, 5, 11, 5};
[Link]("Can Partition: " + canPartition(nums)); // true
}

static int lcs(String s1, String s2) {


int m = [Link](), n = [Link]();
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = [Link](i-1)==[Link](j-1)
? 1+dp[i-1][j-1] : [Link](dp[i-1][j], dp[i][j-1]);
return dp[m][n];
}

static String printLCS(String s1, String s2) {


int m = [Link](), n = [Link]();
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = [Link](i-1)==[Link](j-1)
? 1+dp[i-1][j-1] : [Link](dp[i-1][j], dp[i][j-1]);
StringBuilder sb = new StringBuilder();
int i = m, j = n;
while (i > 0 && j > 0) {
if ([Link](i-1)==[Link](j-1)) { [Link]([Link](i-1)); i--;
j--; }
else if (dp[i-1][j] > dp[i][j-1]) i--; else j--;
}
return [Link]().toString();
}
static int editDistance(String s1, String s2) {
int m = [Link](), n = [Link]();
int[] prev = new int[n+1], curr = new int[n+1];
for (int j = 0; j <= n; j++) prev[j] = j;
for (int ii = 1; ii <= m; ii++) {
curr[0] = ii;
for (int j = 1; j <= n; j++)
curr[j] = [Link](ii-1)==[Link](j-1) ? prev[j-1]
: 1+[Link](prev[j-1], [Link](prev[j], curr[j-1]));
int[] t = prev; prev = curr; curr = t;
}
return prev[n];
}

static int lis(int[] nums) {


int n = [Link]; int[] dp = new int[n]; [Link](dp,1); int max=1;
for (int i=1;i<n;i++){for(int j=0;j<i;j++) if(nums[j]<nums[i])
dp[i]=[Link](dp[i],dp[j]+1); max=[Link](max,dp[i]);}
return max;
}

static int lisOptimal(int[] nums) {


List<Integer> tails = new ArrayList<>();
for (int num : nums) {
int lo=0, hi=[Link]();
while (lo<hi){int mid=(lo+hi)/2; if([Link](mid)<num) lo=mid+1; else
hi=mid;}
if (lo==[Link]()) [Link](num); else [Link](lo,num);
}
return [Link]();
}

static int uniquePathsOptimal(int m, int n) {


int[] dp = new int[n]; [Link](dp,1);
for (int i=1;i<m;i++) for (int j=1;j<n;j++) dp[j]+=dp[j-1];
return dp[n-1];
}

static int minPathSum(int[][] g) {


int m=[Link], n=g[0].length; int[][] dp=new int[m][n]; dp[0][0]=g[0][0];
for(int i=1;i<m;i++) dp[i][0]=dp[i-1][0]+g[i][0];
for(int j=1;j<n;j++) dp[0][j]=dp[0][j-1]+g[0][j];
for(int i=1;i<m;i++) for(int j=1;j<n;j++) dp[i][j]=g[i][j]+[Link](dp[i-1]
[j],dp[i][j-1]);
return dp[m-1][n-1];
}

static int lpsDirect(String s) {


int n=[Link](); int[][] dp=new int[n][n];
for(int i=0;i<n;i++) dp[i][i]=1;
for(int len=2;len<=n;len++) for(int i=0;i<=n-len;i++){int j=i+len-1; dp[i]
[j]=[Link](i)==[Link](j)?2+(len>2?dp[i+1][j-1]:0):[Link](dp[i+1][j],dp[i][j-
1]);}
return dp[0][n-1];
}

static int matrixChain(int[] dims) {


int n=[Link]-1; 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]=[Link](dp[i][j],dp[i][k]
+dp[k+1][j]+dims[i]*dims[k+1]*dims[j+1]);}
return dp[0][n-1];
}
static int maxCoins(int[] nums) {
int n=[Link]; int[] arr=new int[n+2]; arr[0]=arr[n+1]=1;
for(int i=0;i<n;i++) arr[i+1]=nums[i];
int[][] dp=new int[n+2][n+2];
for(int len=1;len<=n;len++) for(int i=1;i<=n-len+1;i++){int j=i+len-1;
for(int k=i;k<=j;k++) dp[i][j]=[Link](dp[i][j],dp[i][k-1]+arr[i-
1]*arr[k]*arr[j+1]+dp[k+1][j]);}
return dp[1][n];
}

static boolean canPartition(int[] nums) {


int total=0; for(int n:nums) total+=n;
if(total%2!=0) return false;
int target=total/2; boolean[] dp=new boolean[target+1]; dp[0]=true;
for(int num:nums) for(int j=target;j>=num;j--) dp[j]=dp[j]||dp[j-num];
return dp[target];
}
}

12 Practice Problems for Chapter 23

Solve in this order:

Difficulty Problem
Medium Longest Common Subsequence (LeetCode #1143)
Medium Edit distance (LeetCode #72)
Medium Longest Increasing Subsequence (LeetCode #300)
Medium Unique paths (LeetCode #62)
Medium Minimum path sum (LeetCode #64)
Medium Partition equal subset sum (LeetCode #416)
Medium Longest palindromic subsequence (LeetCode #516)
Hard Burst balloons (LeetCode #312)
Hard Distinct subsequences (LeetCode #115)
Hard Regular expression matching (LeetCode #10)

13 DP Patterns Master Map

1D DP → Fibonacci, Climbing Stairs, House Robber, Coin Change



2D DP → LCS, Edit Distance, Knapsack, Unique Paths

Interval DP → Matrix Chain, Burst Balloons, Palindrome Partition

DP on Graphs → Dijkstra, Bellman-Ford, Floyd-Warshall

💡 Key Insight: Advanced DP problems all share one secret — think about what the LAST action was.
• In Burst Balloons → which balloon was burst LAST?
• In Matrix Chain → where was the LAST split?
This reversal of thinking is what makes hard DP problems click.

Next is Chapter 24 — Advanced Problem Solving, where you combine multiple DSA techniques
together to solve the hardest interview problems! 🚀

You might also like