Java Program to Find Longest Common Subsequence
int[][] dp = new int[m+1][n+1] to build the solution and backtrack to get the subsequence.Examples
How to Think About It
Algorithm
Code
public class LCS { public static String longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (s1.charAt(i - 1) == s2.charAt(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]); } } } StringBuilder sb = new StringBuilder(); int i = m, j = n; while (i > 0 && j > 0) { if (s1.charAt(i - 1) == s2.charAt(j - 1)) { sb.append(s1.charAt(i - 1)); i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } return sb.reverse().toString(); } public static void main(String[] args) { String s1 = "ABCBDAB"; String s2 = "BDCABA"; System.out.println("Longest Common Subsequence: " + longestCommonSubsequence(s1, s2)); } }
Dry Run
Let's trace the example s1 = "ABCBDAB" and s2 = "BDCABA" through the code
Initialize dp array
Create a 8x7 dp array filled with zeros (since s1 length=7, s2 length=6)
Fill dp table
Compare characters and fill dp: for example, dp[1][1] compares 'A' and 'B' (no match, max of dp[0][1], dp[1][0] = 0), dp[2][2] compares 'B' and 'D' (no match), dp[2][1] compares 'B' and 'B' (match, dp[1][0]+1=1), etc.
Backtrack to find subsequence
Start at dp[7][6], move diagonally when characters match, else move to the larger dp neighbor, collecting characters 'B', 'C', 'B', 'A' in reverse order.
Build result
Reverse collected characters to get 'BCBA' as the longest common subsequence.
| i | j | s1.charAt(i-1) | s2.charAt(j-1) | dp[i][j] |
|---|---|---|---|---|
| 1 | 1 | A | B | 0 |
| 2 | 1 | B | B | 1 |
| 3 | 2 | C | D | 1 |
| 4 | 3 | B | C | 1 |
| 5 | 4 | D | A | 2 |
| 6 | 5 | A | B | 3 |
| 7 | 6 | B | A | 3 |
Why This Works
Step 1: Building the dp table
The dp table stores lengths of longest common subsequences for substrings of s1 and s2, allowing reuse of previous results.
Step 2: Filling dp with matches and max values
If characters match, increase length by 1 from the diagonal cell; otherwise, take the maximum from left or top cell to keep the longest subsequence length.
Step 3: Backtracking to find subsequence
Starting from the bottom-right, move diagonally when characters match to collect subsequence characters; otherwise, move towards the cell with the larger dp value.
Alternative Approaches
public class LCSRecursive { public static int lcsLength(String s1, String s2, int i, int j, int[][] memo) { if (i == s1.length() || j == s2.length()) return 0; if (memo[i][j] != -1) return memo[i][j]; if (s1.charAt(i) == s2.charAt(j)) { memo[i][j] = 1 + lcsLength(s1, s2, i + 1, j + 1, memo); } else { memo[i][j] = Math.max(lcsLength(s1, s2, i + 1, j, memo), lcsLength(s1, s2, i, j + 1, memo)); } return memo[i][j]; } public static void main(String[] args) { String s1 = "ABCBDAB"; String s2 = "BDCABA"; int[][] memo = new int[s1.length()][s2.length()]; for (int[] row : memo) java.util.Arrays.fill(row, -1); System.out.println("Length of LCS: " + lcsLength(s1, s2, 0, 0, memo)); } }
public class LCSSpaceOpt { public static int lcsLength(String s1, String s2) { int m = s1.length(), n = s2.length(); int[] prev = new int[n + 1]; for (int i = 1; i <= m; i++) { int[] curr = new int[n + 1]; for (int j = 1; j <= n; j++) { if (s1.charAt(i - 1) == s2.charAt(j - 1)) { curr[j] = prev[j - 1] + 1; } else { curr[j] = Math.max(prev[j], curr[j - 1]); } } prev = curr; } return prev[n]; } public static void main(String[] args) { String s1 = "ABCBDAB"; String s2 = "BDCABA"; System.out.println("Length of LCS: " + lcsLength(s1, s2)); } }
Complexity: O(m*n) time, O(m*n) space
Time Complexity
The algorithm uses two nested loops over the lengths of the two strings, resulting in O(m*n) time.
Space Complexity
It uses a 2D array of size (m+1)*(n+1) to store intermediate results, so space is O(m*n).
Which Approach is Fastest?
The bottom-up dp approach is fastest for large inputs; recursive memoization is simpler but slower; space-optimized dp saves memory but can't reconstruct subsequence.
| Approach | Time | Space | Best For |
|---|---|---|---|
| Bottom-up DP with reconstruction | O(m*n) | O(m*n) | Finding subsequence and length |
| Recursive with memoization | O(m*n) | O(m*n) | Simple code for length only |
| Space-optimized DP | O(m*n) | O(n) | Memory saving for length only |