Sequence Alignment & LCS
Two strings can be compared by how much of one appears inside the other. The longest common subsequence (LCS) and edit distance are the two classic measures, and they are the same dynamic program with different costs.
╌╌╌╌
How similar are two strings? algorithm and altruistic share the letters
a, l, t, i, c in order, and that shared string, the longest common
subsequence, is one of the most useful measures of similarity in computing. It
underlies the diff utility, version-control merges, and (with a change of cost
function) the alignment of DNA and protein sequences in computational biology.1
This lesson develops the LCS dynamic program in full, then shows that edit
distance is the same dynamic program with the costs rearranged.
The problem
A subsequence of a string is what remains after deleting zero or more
characters, keeping the rest in their original order. It need not be
contiguous: ace is a subsequence of abcde, but aec is not. Given two
strings and , a common
subsequence is a string that is a subsequence of both, and we want a longest
one.
A common subsequence is a set of order-preserving matches threading the two strings: the matched letters appear in both, left to right, though the gaps between them differ.
A brute-force search is hopeless: has subsequences, and checking each against gives . We need the recipe, and it pays to follow it literally. The DP recipe runs through fixed steps: (0) simplify the goal, (1) define the subproblems and notation, (2) write the DP equations, (3) prove them correct by induction, (4) turn them into iterative pseudocode, (5) return to the original goal, then analyze. We walk LCS through those steps verbatim.
Step 0–1: Simplify the goal, define the subproblem
Simplify first. Computing the string drags around bookkeeping; computing its length is cleaner. So we first solve for the LCS length, then recover an actual subsequence in a cheap second pass (Step 5). With that simplification, the decisive move, which recurs across all sequence DPs, is to index subproblems by prefixes of the two strings. Write and for the two inputs.
The answer we want is . There are subproblems, one per pair of prefix lengths and .
Step 2: The DP equations
Now apply optimal substructure by looking at the last characters, and .2 Write the recurrence as a single over three cases, plus a base case:
Read the three branches as moves on the prefixes:
- Case 1 drops : an LCS of and is a common subsequence of and , so .
- Case 2 drops symmetrically, so .
- Case 3 fires only when : the shared character can end an optimal LCS, so we append it to the best LCS of the strictly shorter prefixes, giving .
Why exactly these three? Because every common subsequence of and falls into one of three shapes: it ignores (case 1), ignores (case 2), or uses both as a final match (case 3, possible only if ). The over the applicable cases is the longest of them. When , only cases 1 and 2 apply, and the recurrence collapses to .
Each entry depends only on its left, upper, and upper-left neighbors, so filling the table row by row, left to right (or column by column) respects every dependency. The same three-neighbour stencil drives every sequence DP below: only the labels on the arrows change.
Step 3: Correctness by induction on
Step 4: Iterative pseudocode
The equations are non-circular, since every entry reads strictly smaller prefixes, so they convert directly into a bottom-up table fill. Allocate , zero the border, then sweep:
- 1for to do
- 2empty prefix
- 3for to do
- 4empty prefix
- 5for to do
- 6for to do
- 7cases 1, 2
- 8if then
- 9case 3
- 10return
Every cell costs , so the fill is .
The DP table, filled
Take and . We build the table of values. Row and column are all zero (empty prefix); every other cell is filled by the recurrence. The shaded diagonal steps mark the matches that build the answer, and the red arrows trace the reconstruction walk (Step 5) backwards from the corner.
The bottom-right entry reads : the longest common
subsequence of BDCAB and ABCB has length , namely BCB (the only one here,
though in general there may be ties). The arrows enter each shaded match cell
diagonally (emitting B, then C, then B, read from the corner upward) and step
straight up or left through non-match cells, exactly as the reconstruction below
prescribes.
Step 5: Reconstructing the subsequence
The table gives the length; this is where the Step 0 simplification is paid back. In a second pass we walk backwards from , undoing the recurrence. At cell : if , that character belongs to the LCS — emit it and step diagonally to (case 3); otherwise move to whichever neighbor, up or left, holds the larger value (the one the of cases 1 and 2 chose).
- 1if or then
- 2return the empty stringempty prefix
- 3if then
- 4return followed bycase 3 match
- 5else if then
- 6returnfrom above (case 1)
- 7else
- 8returnfrom left (case 2)
The walk takes one step toward the origin each call, so it runs in time, cheap compared with building the table.
Trace it on the worked table above, starting at with value . The red arrows in the figure are this walk:
- : , a match. Emit
B, step to . - : . Compare the up neighbour against the left neighbour ; the tie breaks upward, step to .
- : , a match. Emit
C, step to . - : . Up neighbour ties left neighbour ; step up to .
- : , a match. Emit
B, step to . - : , stop.
The emitted characters, corner-first, are B, C, B; reversed into forward order
they read BCB — the length- LCS the corner promised. The tie-break rule (up
before left) is arbitrary; the other choice would recover an equally long
subsequence, and a longest common subsequence need not be unique.
Running time and space
The table has entries. Filling one costs a character comparison and a of at most three previously-computed neighbours, all . Summing over the fill,
and the border initialization adds only , which absorbs. The reconstruction pass is , also absorbed. LCS therefore runs in time — a decisive improvement over the brute force, and for two length- strings the difference is a million cell updates against roughly subsequence checks.
Space is for the full table, but the recurrence reads only the current row and the one directly above it. Keeping two length- rows and swapping them after each (a rolling array) drops the footprint to , and choosing the shorter string as the inner axis makes it .
The catch is universal to this trick: collapsing the table erases the information the traceback needs, so the two-row version yields only the length, never the subsequence itself. Recovering the actual alignment in linear space is possible — Hirschberg's divide-and-conquer refinement does it in time and space3 — but that machinery is a topic for later.
The same machine: edit distance
Edit distance (the Levenshtein distance) asks the closely related
question: what is the minimum number of single-character insertions,
deletions, and substitutions that transform into ?4 It is the cost
model behind spell-checkers and diff, and it is structurally identical to LCS.
Again we look at the last characters. If , they need no edit and we align them for free. Otherwise we make one of three moves (delete , insert , or substitute ), each at cost , and recurse on the correspondingly shorter prefixes:
The base cases say it: turning a length- prefix into the empty string costs deletions, and building a length- prefix from nothing costs insertions.
It is the same three-neighbour stencil as LCS — only the arrow labels change. The diagonal is free on a match and costs to substitute; a step down deletes , a step right inserts , each at cost . We take the cheapest incoming move instead of the longest:
Filled on a small pair, the table looks just like the LCS one but now minimizes. Take and : the shaded diagonal marks the free matches, and the corner reports the answer.
Reading the corner, . The traceback recovers the alignment itself, walking corner to origin and reading each step as the edit that produced it:
- value : the left neighbour is one cheaper, so this step is an insertion of . Move left to .
- value : , and the upper-left
is the cheapest source, so this is a substitution
TR. Move diagonally to . - value : , a free match. Move diagonally to .
- value : , a free match. Move to and stop.
Read forward, the alignment is: keep C, keep A, substitute T R, insert
S — turning CAT into CARS in the promised two edits. The match cells on the
diagonal ( and ) copy the upper-left value unchanged, the
same LCS diagonal step, but counting saved edits instead of matched characters.
The fill is the LCS loop with in place of and the borders seeded to the prefix lengths rather than zeros:
- 1for to do
- 2delete all of
- 3for to do
- 4insert all of
- 5for to do
- 6for to do
- 7if then
- 8free match
- 9else
- 10delete, insert, substitute
- 11return
Every cell is still , so the fill is , and the alignment is recovered by the same corner-to-origin traceback as LCS.
Compare this against LCS line by line. Both index subproblems by prefix pairs;
both branch on whether the last characters match; both fill an
table where each entry reads its left, upper, and upper-left
neighbors; both run in . The only differences are the costs and
the optimization direction: LCS maximizes matched characters, edit distance
minimizes edits. Sequence DPs are a single template, parameterized by
what a match
earns and a mismatch
costs. Recognize the template and a whole
family of problems (LCS, edit distance, sequence alignment, longest common
substring, and string matching) falls
to the same code.
Alignment, bioinformatics, and the quadratic wall
The LCS/edit-distance template is the single most consequential dynamic program in
applied computing, because it is sequence alignment. Needleman and Wunsch
(1970) introduced the global-alignment DP for comparing protein and
nucleotide sequences; Smith and Waterman (1981) adapted it to local alignment
(the best-matching substring pair, by clamping the score at and tracking the
global maximum, the same move the maximum-subarray
DP makes). These two recurrences are the foundation of computational biology, and
the diff utility, git's merge machinery, spell checkers, and DNA read-mapping
all descend from the same table. Gotoh (1982) refined the model with affine gap
penalties — charging a large cost to open a gap and a small cost to extend it,
which needs three coupled tables but stays — because a single long
insertion is biologically more plausible than many scattered ones.
The catch is scale. A table is fine for two short strings but ruinous
for two human chromosomes, and the Hirschberg linear-space
trick (noted above) fixes the memory
but not the time. Whether the time can be beaten is now settled conditionally:
Backurs and Indyk (2015) and Bringmann and Künnemann (2015) proved that edit
distance and LCS admit no strongly subquadratic algorithm
unless the Strong Exponential Time Hypothesis is false. So the quadratic table is a
genuine wall, and the practical response has been to give up exactness: heuristic
aligners like BLAST (Altschul et al., 1990) and FASTA seed on short exact
matches and extend them, trading a guarantee of optimality for the speed that made
genome-scale search possible. The abstract define the subproblem, fill the table
discipline of this lesson is, in this one instance, a multi-billion-dollar tool.5
Takeaways
- Index sequence subproblems by prefixes: is the key to LCS, after the Step 0 move of solving for length first.
- The recurrence is a over three cases (drop in case 1, drop in case 2, or extend the diagonal by when in case 3) over a base case of for an empty prefix.
- Prove it by induction on in two directions: (build a witness subsequence) and (every common subsequence fits one of the three cases).
- Fill the table in ; reconstruct in a second pass, walking backwards from and emitting a character on every diagonal match.
- Only the length is needed? Two rows give space, at the cost of losing the reconstruction.
- Edit distance is the same dynamic program: same prefix subproblems, same table shape, same , minimizing edits instead of maximizing matches. Sequence DP is one reusable template.
Footnotes
- Skiena, §10 — Dynamic Programming: the longest common subsequence as a similarity measure underlying
diffand sequence alignment. ↩ - CLRS, Ch. 15 — Dynamic Programming: the LCS recurrence obtained by examining the last characters of each prefix. ↩
- Erickson, Ch. 3 — Dynamic Programming: Hirschberg's divide-and-conquer computes an optimal alignment in linear space by recursing on the midpoint column, keeping the time bound. ↩
- Erickson, Ch. 3 — Dynamic Programming: edit (Levenshtein) distance as the minimum-cost insert/delete/substitute alignment filling an table. ↩
- Needleman & Wunsch (1970) global and Smith & Waterman (1981) local sequence alignment; Gotoh (1982) affine gaps. Backurs & Indyk (2015) and Bringmann & Künnemann (2015): no strongly subquadratic edit distance / LCS under SETH — the practical reason heuristic aligners like BLAST (Altschul et al., 1990) exist. ↩
╌╌ END ╌╌