Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Dynamic programming

Edit distance

Lesson · 11 min read · Dynamic programming · Edit distance

The fewest insertions, deletions and substitutions that turn one string into another. Three choices per cell, one free match.

The problem

Turn horse into ros with the fewest single-character edits: insert, delete or replace a character. One answer: replace h with r (rorse), delete r (rose), delete e (ros): three edits. Spell checkers, DNA alignment and fuzzy search all compute this.

State and recurrence

Let D(i, j) be the fewest edits turning the prefix a[:i] into b[:j]. Look at the last characters:

Recurrence

D(i, j)

  1. if a[i − 1] = b[j − 1]: D(i − 1, j − 1), free;
  2. otherwise 1 + min of: D(i − 1, j) (delete a[i − 1]), D(i, j − 1) (insert b[j − 1]), D(i − 1, j − 1) (replace).

base cases

  1. D(i, 0) = i (delete everything), D(0, j) = j (insert everything).

Forgetting the free match, and always paying 1, is the classic bug. It fails the coverage check: the solution that keeps matching characters has no path.

Bottom-up

D(i, j) for horse (rows) and ros (columns).

  1. Step 1 / 16
    ∅ros∅0123h1o2r3s4e5

    Base cases filled first.

  2. Step 2 / 16
    ∅ros∅0123h11o2r3s4e5

    'h' ≠ 'r': 1 + min(delete 1, insert 1, replace 0) = 1.

  3. Step 3 / 16
    ∅ros∅0123h112o2r3s4e5

    'h' ≠ 'o': 1 + min(delete 2, insert 1, replace 1) = 2.

  4. Step 4 / 16
    ∅ros∅0123h1123o2r3s4e5

    'h' ≠ 's': 1 + min(delete 3, insert 2, replace 2) = 3.

  5. Step 5 / 16
    ∅ros∅0123h1123o22r3s4e5

    'o' ≠ 'r': 1 + min(delete 1, insert 2, replace 1) = 2.

  6. Step 6 / 16
    ∅ros∅0123h1123o221r3s4e5

    'o' = 'o': keep it for free, 1.

  7. Step 7 / 16
    ∅ros∅0123h1123o2212r3s4e5

    'o' ≠ 's': 1 + min(delete 3, insert 1, replace 2) = 2.

  8. Step 8 / 16
    ∅ros∅0123h1123o2212r32s4e5

    'r' = 'r': keep it for free, 2.

  9. Step 9 / 16
    ∅ros∅0123h1123o2212r322s4e5

    'r' ≠ 'o': 1 + min(delete 1, insert 2, replace 2) = 2.

  10. Step 10 / 16
    ∅ros∅0123h1123o2212r3222s4e5

    'r' ≠ 's': 1 + min(delete 2, insert 2, replace 1) = 2.

  11. Step 11 / 16
    ∅ros∅0123h1123o2212r3222s43e5

    's' ≠ 'r': 1 + min(delete 2, insert 4, replace 3) = 3.

  12. Step 12 / 16
    ∅ros∅0123h1123o2212r3222s433e5

    's' ≠ 'o': 1 + min(delete 2, insert 3, replace 2) = 3.

  13. Step 13 / 16
    ∅ros∅0123h1123o2212r3222s4332e5

    's' = 's': keep it for free, 2.

  14. Step 14 / 16
    ∅ros∅0123h1123o2212r3222s4332e54

    'e' ≠ 'r': 1 + min(delete 3, insert 5, replace 4) = 4.

  15. Step 15 / 16
    ∅ros∅0123h1123o2212r3222s4332e544

    'e' ≠ 'o': 1 + min(delete 3, insert 4, replace 3) = 4.

  16. Step 16 / 16
    ∅ros∅0123h1123o2212r3222s4332e5443

    'e' ≠ 's': 1 + min(delete 2, insert 4, replace 3) = 3. D(5, 3) = 3: three edits.

Code and cost

def edit_distance(a, b):
    prev = list(range(len(b) + 1))             # row i = 0
    for i in range(1, len(a) + 1):
        cur = [i] + [0] * len(b)
        for j in range(1, len(b) + 1):
            if a[i - 1] == b[j - 1]:
                cur[j] = prev[j - 1]           # match: free
            else:
                cur[j] = 1 + min(prev[j], cur[j - 1], prev[j - 1])
        prev = cur
    return prev[len(b)]
TimeO(n · m)
SpaceO(m) with two rows

The problem in this module asks for the same distance in six languages, with the full solution walkthrough.