Longest common subsequence (LCS)
Find the longest subsequence that appears in both strings (in the same order, not necessarily side by side) with a dynamic-programming table.
A subsequence comes from deleting some characters (not necessarily side by side). We want the longest subsequence common to two strings; for example to compare files (diff) or DNA.
Read the full explanation ↓- Time:
O(m·n) - Space:
O(m·n) - CLRS, section 14.4
X = ABCBDAB and Y = BDCABA; the LCS has length 4, and one answer is BCBA.
def lcs(X, Y):m, n = len(X), len(Y)# c[i][j] = the LCS length of X[:i] and Y[:j]; row 0 and column 0 are the empty prefixes.c = [[0] * (n + 1) for _ in range(m + 1)]for i in range(1, m + 1):for j in range(1, n + 1):if X[i - 1] == Y[j - 1]:c[i][j] = c[i - 1][j - 1] + 1elif c[i - 1][j] >= c[i][j - 1]:c[i][j] = c[i - 1][j]else:c[i][j] = c[i][j - 1]# Walk back from the corner to read one longest common subsequence.out = []i, j = m, nwhile i > 0 and j > 0:if X[i - 1] == Y[j - 1]:out.append(X[i - 1])i, j = i - 1, j - 1elif c[i - 1][j] >= c[i][j - 1]:i = i - 1else:j = j - 1return "".join(reversed(out))
Start
The table is still empty.
Local variables
Orange = changed in this step
Call stack
- lcs()line 1
Explanation
The idea
A subsequence comes from deleting some characters (not necessarily side by side). We want the longest subsequence common to two strings; for example to compare files (diff) or DNA.
Dynamic programming
c[i][j] is the LCS length of X[:i] and Y[:j]:
- if the last characters are equal, that character is in the LCS:
c[i−1][j−1] + 1; - otherwise one of the two last characters is not in it:
max(c[i−1][j], c[i][j−1]).
The table fills row by row, and walking back from the bottom-right corner reads off the LCS itself.
Cost
(m + 1)(n + 1) cells at O(1) each: O(m·n); against the 2ᵐ possible subsequences of the brute-force method.
What to watch in the trace
- The table
c(row 0 and column 0 are the empty prefixes), andout, which fills from the end during the walk back.