Dynamic Programming
Longest Common Subsequence
Find the longest sequence common to two strings: subsequences vs substrings, the LCS table and recurrence, traceback, and the related edit-distance problem.
A subsequence keeps characters in order but may skip some: "ACE" is a subsequence of "ABCDE". (A substring must be contiguous.) The longest common subsequence (LCS) of two strings is the longest sequence that is a subsequence of both.
LCS powers diff tools, version control, DNA sequence alignment and plagiarism detection.
Recurrence
State: L[i][j] = length of the LCS of the first i characters of X and the first j characters of Y.
L[i][j] = 0 if i = 0 or j = 0 L[i][j] = L[i−1][j−1] + 1 if X[i] = Y[j] L[i][j] = max(L[i−1][j], L[i][j−1]) otherwise
If the last characters match, they extend the LCS of the shorter prefixes. If not, drop one of them — whichever gives the longer result.
Watch it fill
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string X = "ABCBDAB", Y = "BDCABA";
int m = X.size(), n = Y.size();
vector<vector<int>> L(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
L[i][j] = (X[i - 1] == Y[j - 1]) ? L[i - 1][j - 1] + 1 : max(L[i - 1][j], L[i][j - 1]);
cout << " ";
for (char c : Y) cout << " " << c;
cout << endl;
for (int i = 0; i <= m; i++) {
cout << (i ? X[i - 1] : ' ') << " ";
for (int j = 0; j <= n; j++) cout << " " << L[i][j];
cout << endl;
}
string lcs; // trace back from the bottom-right corner
for (int i = m, j = n; i > 0 && j > 0; ) {
if (X[i - 1] == Y[j - 1]) { lcs += X[i - 1]; i--; j--; }
else if (L[i - 1][j] >= L[i][j - 1]) i--;
else j--;
}
reverse(lcs.begin(), lcs.end());
cout << "LCS length " << L[m][n] << ": " << lcs << endl;
return 0;
}B D C A B A 0 0 0 0 0 0 0 A 0 0 0 0 1 1 1 B 0 1 1 1 1 2 2 C 0 1 1 2 2 2 2 B 0 1 1 2 2 3 3 D 0 1 2 2 2 3 3 A 0 1 2 2 3 3 4 B 0 1 2 2 3 4 4 LCS length 4: BCBA
Time and space O(mn). Brute force would check all 2ᵐ subsequences of X.
Related: edit distance
The edit distance (Levenshtein distance) is the minimum number of single-character insertions, deletions and substitutions that turn one string into another — the basis of spell checkers.
D[i][j] = D[i−1][j−1] if X[i] = Y[j] D[i][j] = 1 + min(D[i−1][j] (delete), D[i][j−1] (insert), D[i−1][j−1] (substitute)) otherwise
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int editDistance(const string &a, const string &b) {
vector<vector<int>> D(a.size() + 1, vector<int>(b.size() + 1));
for (size_t i = 0; i <= a.size(); i++) D[i][0] = i;
for (size_t j = 0; j <= b.size(); j++) D[0][j] = j;
for (size_t i = 1; i <= a.size(); i++)
for (size_t j = 1; j <= b.size(); j++)
D[i][j] = a[i - 1] == b[j - 1] ? D[i - 1][j - 1]
: 1 + min({D[i - 1][j], D[i][j - 1], D[i - 1][j - 1]});
return D[a.size()][b.size()];
}
int main() {
cout << "kitten -> sitting: " << editDistance("kitten", "sitting") << endl;
cout << "sunday -> saturday: " << editDistance("sunday", "saturday") << endl;
cout << "algorithm -> altruistic: " << editDistance("algorithm", "altruistic") << endl;
return 0;
}kitten -> sitting: 3 sunday -> saturday: 3 algorithm -> altruistic: 6
Test yourself
4 questions · pick an answer to check it instantly
- 1
Which is a subsequence of "PROGRAM" but not a substring?
P, G, M appear in order but not next to each other. - 2
If X[i] = Y[j], what is L[i][j]?
Matching last characters extend the LCS of both shorter prefixes. - 3
What is the LCS length of "ABCBDAB" and "BDCABA"?
For example "BCBA". - 4
What is the edit distance between "cat" and "cut"?
One substitution (a → u).