Longest Common Subsequence
Imagine two friends kept diaries over the summer. Their entries don't match word-for-word, but there's a common thread — shared moments they both wrote about, in the same order, even if other entries appear in between. Finding that longest shared storyline is exactly the Longest Common Subsequence (LCS) problem.
A subsequence keeps relative order but can skip elements. So 'ACE' is a subsequence of 'ABCDE' — you skip B and D. A substring must be contiguous — 'BCD' is a substring, 'ACE' is not. LCS finds the longest subsequence that appears in both strings. It is closely related to Edit Distance, which measures the minimum operations to transform one string into another.
The 2D DP Table
Define dp[i][j] = length of the LCS of the first i characters of s and the first j characters of t. The table is (m+1) × (n+1) — one extra row and column for the "empty prefix" base cases: dp[0][j] = 0 and dp[i][0] = 0 (LCS with an empty string is always 0).
The Recurrence
For each cell (i, j):
If s[i-1] == t[j-1] (characters match): dp[i][j] = dp[i-1][j-1] + 1. We extend the LCS of the two shorter prefixes by this matching character.
If they don't match: dp[i][j] = max(dp[i-1][j], dp[i][j-1]). We take the best LCS we can get by either ignoring the last character of s or the last character of t.
Final answer: dp[m][n].
Tracing Back the Actual Sequence
The table gives the length. To find the actual LCS, trace back from dp[m][n]: if s[i-1] == t[j-1], this character is in the LCS — record it and move diagonally to (i-1, j-1). Otherwise, move toward the larger neighbor (up or left). Collect characters during diagonal moves (reverse the order at the end).
Space Optimization
dp[i][j] only depends on dp[i-1][j], dp[i-1][j-1], and dp[i][j-1] — just the previous row and one extra variable. You can reduce to \(O(min(m,n)\)) space by keeping only two rows. The tradeoff: you lose the ability to trace back the actual sequence.