← Back to DSA Course | Chapter 14: Dynamic Programming | Lesson 6 of 12

Longest Common Subsequence

LCS Idea

The longest common subsequence of two strings is the longest sequence of characters that appears in both, in the same relative order, but not necessarily touching — ace is a subsequence of abcde even though the letters aren't adjacent.

Example: LCS Idea

#include <iostream>
using namespace std;
int main() {
	string a = "abcde", b = "ace";
	cout << "'" << b << "' is a subsequence of '" << a << "' (same order, not necessarily touching)";
	return 0;
}
public class Main {
	public static void main(String[] args) {
		String a = "abcde", b = "ace";
		System.out.println("'" + b + "' is a subsequence of '" + a + "' (same order, not necessarily touching)");
	}
}
a, b = "abcde", "ace"
print(f"'{b}' is a subsequence of '{a}' (same order, not necessarily touching)")
#include <stdio.h>
int main() {
	char a[] = "abcde", b[] = "ace";
	printf("'%s' is a subsequence of '%s' (same order, not necessarily touching)", b, a);
	return 0;
}

DP State

dp[i][j] stores the LCS length using only the first i characters of the first string and the first j characters of the second, so filling this table prefix by prefix builds up to the answer for the full strings.

Example: DP State

#include <iostream>
using namespace std;
int main() {
	int dp[6][4] = {0};
	cout << "dp[i][j] = LCS length using first i chars of string1, first j chars of string2";
	return 0;
}
public class Main {
	public static void main(String[] args) {
		int[][] dp = new int[6][4];
		System.out.println("dp[i][j] = LCS length using first i chars of string1, first j chars of string2");
	}
}
dp = [[0]*4 for _ in range(6)]
print("dp[i][j] = LCS length using first i chars of string1, first j chars of string2")
#include <stdio.h>
int main() {
	int dp[6][4] = {0};
	printf("dp[i][j] = LCS length using first i chars of string1, first j chars of string2");
	return 0;
}

Transition

If the current characters from both strings match, the LCS grows by one from the diagonal value (dp[i-1][j-1] + 1); if they don't match, the best you can do is the better of ignoring one character from either string (max of dp[i-1][j] and dp[i][j-1]).

Example: Transition

#include <iostream>
using namespace std;
int main() {
	string a = "abcde", b = "ace";
	int n = a.size(), m = b.size();
	int dp[6][4] = {0};
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			dp[i][j] = a[i-1]==b[j-1] ? dp[i-1][j-1]+1 : max(dp[i-1][j], dp[i][j-1]);
	cout << "LCS length: " << dp[n][m];
	return 0;
}
public class Main {
	public static void main(String[] args) {
		String a = "abcde", b = "ace";
		int n = a.length(), m = b.length();
		int[][] dp = new int[n+1][m+1];
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				dp[i][j] = a.charAt(i-1)==b.charAt(j-1) ? dp[i-1][j-1]+1 : Math.max(dp[i-1][j], dp[i][j-1]);
		System.out.println("LCS length: " + dp[n][m]);
	}
}
a, b = "abcde", "ace"
n, m = len(a), len(b)
dp = [[0]*(m+1) for _ in range(n+1)]
for i in range(1, n+1):
    for j in range(1, m+1):
        dp[i][j] = dp[i-1][j-1]+1 if a[i-1]==b[j-1] else max(dp[i-1][j], dp[i][j-1])
print("LCS length:", dp[n][m])
#include <stdio.h>
#include <string.h>
int main() {
	char a[] = "abcde", b[] = "ace";
	int n = strlen(a), m = strlen(b);
	int dp[6][4] = {0};
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			if (a[i-1]==b[j-1]) dp[i][j] = dp[i-1][j-1]+1;
			else dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
		}
	printf("LCS length: %d", dp[n][m]);
	return 0;
}

Reconstruct LCS

Beyond just the length, tracing back through the filled table from the bottom-right corner — following diagonal moves on matches and the larger neighbor on mismatches — reconstructs the actual subsequence, not just its length.

Example: Reconstruct LCS

#include <iostream>
using namespace std;
int main() {
	string a = "abcde", b = "ace";
	int n = a.size(), m = b.size();
	int dp[6][4] = {0};
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			dp[i][j] = a[i-1]==b[j-1] ? dp[i-1][j-1]+1 : max(dp[i-1][j], dp[i][j-1]);
	string result; int i = n, j = m;
	while (i > 0 && j > 0) {
		if (a[i-1] == b[j-1]) { result = a[i-1] + result; i--; j--; }
		else if (dp[i-1][j] > dp[i][j-1]) i--;
		else j--;
	}
	cout << "Reconstructed LCS: " << result;
	return 0;
}
public class Main {
	public static void main(String[] args) {
		String a = "abcde", b = "ace";
		int n = a.length(), m = b.length();
		int[][] dp = new int[n+1][m+1];
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				dp[i][j] = a.charAt(i-1)==b.charAt(j-1) ? dp[i-1][j-1]+1 : Math.max(dp[i-1][j], dp[i][j-1]);
		StringBuilder result = new StringBuilder();
		int i = n, j = m;
		while (i > 0 && j > 0) {
			if (a.charAt(i-1) == b.charAt(j-1)) { result.insert(0, a.charAt(i-1)); i--; j--; }
			else if (dp[i-1][j] > dp[i][j-1]) i--;
			else j--;
		}
		System.out.println("Reconstructed LCS: " + result);
	}
}
a, b = "abcde", "ace"
n, m = len(a), len(b)
dp = [[0]*(m+1) for _ in range(n+1)]
for i in range(1, n+1):
    for j in range(1, m+1):
        dp[i][j] = dp[i-1][j-1]+1 if a[i-1]==b[j-1] else max(dp[i-1][j], dp[i][j-1])
result = []
i, j = n, m
while i > 0 and j > 0:
    if a[i-1] == b[j-1]:
        result.append(a[i-1]); i -= 1; j -= 1
    elif dp[i-1][j] > dp[i][j-1]:
        i -= 1
    else:
        j -= 1
print("Reconstructed LCS:", "".join(reversed(result)))
#include <stdio.h>
#include <string.h>
int main() {
	char a[] = "abcde", b[] = "ace", result[10];
	int n = strlen(a), m = strlen(b), dp[6][4] = {0};
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			if (a[i-1]==b[j-1]) dp[i][j] = dp[i-1][j-1]+1;
			else dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
		}
	int i = n, j = m, k = 0;
	while (i > 0 && j > 0) {
		if (a[i-1] == b[j-1]) { result[k++] = a[i-1]; i--; j--; }
		else if (dp[i-1][j] > dp[i][j-1]) i--;
		else j--;
	}
	result[k] = '\0';
	for (int x = 0; x < k/2; x++) { char t = result[x]; result[x] = result[k-1-x]; result[k-1-x] = t; }
	printf("Reconstructed LCS: %s", result);
	return 0;
}

Practice

LCS shows up directly in diff tools that highlight what changed between two versions of a file, and in DNA sequence comparison where researchers look for shared genetic patterns between two sequences.

Example: Practice

#include <iostream>
using namespace std;
int main() {
	cout << "LCS powers diff tools (what changed between file versions) and DNA sequence comparison";
	return 0;
}
public class Main {
	public static void main(String[] args) {
		System.out.println("LCS powers diff tools (what changed between file versions) and DNA sequence comparison");
	}
}
print("LCS powers diff tools (what changed between file versions) and DNA sequence comparison")
#include <stdio.h>
int main() {
	printf("LCS powers diff tools (what changed between file versions) and DNA sequence comparison");
	return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.