← Knowledgebase

Dynamic programming: edit distance & LCS

The idea

Some problems break into smaller versions of themselves that overlap: the same sub-problem comes up again and again. Plain recursion would solve it over and over, exponentially often. Dynamic programming solves each sub-problem once and stores the answer in a table, filling it in an order where everything a cell needs is already there.

Two classic examples compare two words, A (down the side) and B (across the top):

Edit distance

The fewest single-letter inserts, deletes and substitutions that turn A into B. Spell checkers, DNA alignment and diff tools all use it. Cell (i, j) holds the edit distance from the first i letters of A to the first j letters of B.

  • Row 0 and column 0: turning nothing into j letters takes j inserts; i letters into nothing takes i deletes.
  • If the letters at row i and column j match: copy the diagonal ↖. No edit needed.
  • Otherwise: 1 + the smallest of ↑ (delete A's letter), ← (insert B's letter) and ↖ (substitute).

Longest common subsequence (LCS)

The longest sequence of letters that appears in both words in the same order, though not necessarily side by side. LCS of ABCBD and BDCAB has length 3 (for example BCB). It is the core of tools like diff.

  • Row 0 and column 0 are 0: an empty word has nothing in common with anything.
  • If the letters match: diagonal ↖ + 1. This letter extends the common subsequence.
  • Otherwise: the larger of ↑ and ←. Drop one of the two letters and keep the better result.

Reading the answer back: traceback

The bottom-right cell holds the final number, but not the edits or the letters themselves. To get them, walk back from that cell: at each step, move to the neighbour the value came from. A diagonal move is a keep or substitution (or, for LCS, a letter of the subsequence); up is a delete; left is an insert. When several moves fit, these pages prefer diagonal, then up, then left.

Both problems
TimeO(m · n): one constant-time step per cell.
SpaceO(m · n) for the full table (needed for traceback); just two rows if you only want the number.