Edit Distance
Edit distance (Levenshtein distance) counts the fewest single-character insertions, deletions, and substitutions needed to turn one string into another — the same table shape as longest common subsequence, with a third option per cell.
Prerequisites: Longest Common Subsequence
A spell-checker suggesting "did you mean 'kitten'" when you typed "sitten," or a ticker-symbol matcher tolerating one typo'd character, both need a numeric notion of "how different are these two strings." Edit distance (Levenshtein distance) is that number: the minimum count of single-character insertions, deletions, and substitutions to turn string into string .
The idea: same table as LCS, one more option per cell
Define as the edit distance between the first characters of and the first characters of . If the current characters match, no edit is needed here — inherit the diagonal cell unchanged. If they don't match, an edit is unavoidable, and you take the cheapest of the three possible edits:
In words, the three options on a mismatch are: substitute for (diagonal cell plus one edit), delete (cell above plus one edit — gets one character shorter), or insert into (cell to the left plus one edit — gets one character longer, matching 's prefix). The base cases are (delete all of 's first characters) and (insert all of 's first characters). Time and space are , same shape as Longest Common Subsequence — the only difference is the extra "diagonal + 1" and "left + 1" options on a mismatch, versus LCS's plain max of two neighbors.
Worked example: "cat" to "cut"
Prefixes of ="cat" as rows 0-3, ="cut" as columns 0-3. Row 0 and column 0 are [0,1,2,3] (all inserts/deletes). Row 1 (A="c"): ('c' vs 'c') match → . ('c' vs 'u') mismatch → . ('c' vs 't') mismatch → . Row 2 (A="ca"): ('a' vs 'c') mismatch → . Continuing similarly, (a vs u mismatch, best neighbor 0), . Row 3 (A="cat"): ('t' vs 't') match → . Answer: edit distance 1 — substitute 'a' for 'u'.
def edit_distance(a, b): # O(m*n) time and space
m, n = len(a), len(b)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if a[i-1] == b[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
return dp[m][n]
Edit distance is LCS's table plus one extra move: on a mismatch, take the min of substitute (diagonal), delete (above), insert (left), each costing one edit. On a match, no edit is spent — copy the diagonal cell exactly.
Space can be trimmed from to by keeping only the current and previous row, since each cell only reads from the row directly above and the current row's previous column — a common interview follow-up.
Where it shows up
Interview staples: edit distance itself (LeetCode 72), one edit distance, and DNA/text similarity variants. On a desk, fuzzy matching ticker symbols, company names, or instrument identifiers across data vendors with inconsistent formatting relies on edit distance (or its weighted variants) to decide "these are probably the same entity despite a typo," and it's a standard building block in entity-resolution pipelines for alternative data.
Related concepts
Practice in interviews
Further reading
- Levenshtein, Binary Codes Capable of Correcting Deletions, Insertions, and Reversals (1966)
- Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 15.4, related)