The Longest Common Subsequence (LCS) problem asks for the longest sequence of characters that appears in the same relative order in two strings, but not necessarily contiguously. For example, the LCS of "president" and "providence" is "priden" (length 6). LCS is solved efficiently using dynamic programming in O(m×n) time, where m and n are the lengths of the two sequences. It is widely used in diff tools, DNA sequence analysis, and file comparison utilities.
Longest Common Subsequence (LCS) Implementation in Java
import java.io.*;
class LongestCommonSubsequence {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter length of Sequence 1: ");
int len1 = Integer.parseInt(reader.readLine());
System.out.println("Enter Sequence 1:");
String seq1 = reader.readLine();
System.out.print("Enter length of Sequence 2: ");
int len2 = Integer.parseInt(reader.readLine());
System.out.print("Enter Sequence 2: ");
String seq2 = reader.readLine();
// dpTable[i][j] = length of LCS of seq1[0..i-1] and seq2[0..j-1]
int[][] dpTable = new int[50][50];
// directionTable[i][j] records how we got to dpTable[i][j]:
// '' means seq1[i] == seq2[j] (diagonal -- character is in LCS)
// '|' means we took dpTable[i-1][j] (from above)
// '-' means we took dpTable[i][j-1] (from left)
char[][] directionTable = new char[len1 + 1][len2 + 1];
// Base cases: LCS of empty prefix is 0
for (int i = 0; i <= len1; i++) dpTable[i][0] = 0;
for (int j = 0; j <= len2; j++) dpTable[0][j] = 0;
System.out.println("\nDP Table:");
// Fill the DP table row by row
for (int i = 0; i < len1; i++) {
for (int j = 0; j < len2; j++) {
if (i == 0 || j == 0) {
// Print border cells (value 0) without computing LCS
System.out.print(dpTable[i][j]);
continue;
}
if (seq1.charAt(i) == seq2.charAt(j)) {
// Characters match: extend the LCS by 1
directionTable[i][j] = '';
dpTable[i][j] = dpTable[i - 1][j - 1] + 1;
System.out.print(directionTable[i][j] + dpTable[i][j]);
} else if (dpTable[i - 1][j] >= dpTable[i][j - 1]) {
// LCS from above is at least as long
directionTable[i][j] = '|';
dpTable[i][j] = dpTable[i - 1][j];
System.out.print(directionTable[i][j] + dpTable[i][j]);
} else {
// LCS from the left is longer
directionTable[i][j] = '-';
dpTable[i][j] = dpTable[i][j - 1];
System.out.print(directionTable[i][j] + dpTable[i][j]);
}
}
System.out.println();
}
// Traceback: follow '' arrows to collect LCS character indices
int row = len1 - 1;
int col = len2 - 1;
int[] lcsIndices = new int[10]; // stores row indices of matched characters
int lcsLength = 1; // 1-indexed counter
System.out.print("\nLongest Common Subsequence: ");
while (row > 0 || col > 0) {
if (directionTable[row][col] == '') {
// Diagonal arrow: this position is part of the LCS
lcsIndices[lcsLength] = row;
lcsLength++;
row--;
col--;
} else if (directionTable[row][col] == '|') {
// Arrow from above: move up
row--;
} else {
// Arrow from left: move left
col--;
}
}
// Print LCS characters in forward order (lcsIndices was filled in reverse)
for (int i = lcsLength - 1; i > 0; i--) {
System.out.print(seq1.charAt(lcsIndices[i]));
}
System.out.println();
}
}
How the Code Works
- DP table:
dpTable[i][j]stores the length of the LCS ofseq1[0..i-1]andseq2[0..j-1]. All row-0 and column-0 entries are 0 (base case: LCS with empty string is 0). - Recurrence:
- If
seq1[i] == seq2[j]: the characters match →dpTable[i][j] = dpTable[i-1][j-1] + 1, and a diagonal arrow'\'is recorded. - If
dpTable[i-1][j] >= dpTable[i][j-1]: inherit from above →dpTable[i][j] = dpTable[i-1][j], record'|'. - Otherwise: inherit from left →
dpTable[i][j] = dpTable[i][j-1], record'-'.
- If
- Direction table:
directionTable[i][j]encodes the decision made at each cell, enabling traceback. - Traceback: starting at
dpTable[len1-1][len2-1], following'\'arrows (diagonal matches) collects the LCS character indices in reverse. Non-diagonal arrows just move the pointer without collecting characters. - Print: the collected indices are printed in reverse order to produce the LCS in left-to-right sequence.
Sample Output
Enter length of Sequence 1: 9
Enter Sequence 1: president
Enter length of Sequence 2: 10
Enter Sequence 2: providence
DP Table:
(table rows showing dpTable values and direction arrows)
Longest Common Subsequence: priden
Output Explanation
- The two input strings are
"president"(length 9) and"providence"(length 10). - The DP table is filled by comparing characters at each (row, column) position. Cells with matching characters get a diagonal
'\'arrow and the LCS count increments. - The traceback follows diagonal arrows from the bottom-right corner back to the top-left, collecting matched characters: p, r, i, d, e, n.
- The LCS is
"priden"with length 6. These characters appear in both strings in the same relative left-to-right order, though not necessarily consecutively.
See Also
- Implementing 0-1 Knapsack in Java
- Implementing Greedy (Fractional) Knapsack in Java
- Implementing Merge Sort Algorithm in Java
- N-Queen Problem in Java
- KMP String Matching Algorithm in Java
Conclusion
The LCS algorithm is a foundational dynamic programming problem that teaches the essential pattern: define a subproblem, fill a table bottom-up, then traceback to reconstruct the solution. It is directly related to the edit-distance (Levenshtein) problem and underpins tools like Unix diff. Extending this to more than two sequences or applying it to DNA alignment are natural next steps once the two-sequence version is well understood.