Dynamic Programming · Classic DP Patterns

Edit Distance

Building a 2D table where each cell (i, j) answers "how many single-character insert/delete/replace operations turn the first i characters of one word into the first j characters of the other," with matching characters costing nothing and a mismatch costing one operation plus the cheapest of the three ways to recover from it.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: operations with different costs
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

How different are two strings? "Did you mean...?" suggestions, fuzzy search and DNA alignment all need a number for that: the fewest single-character insertions, deletions and replacements that turn one string into the other. This is the Levenshtein distance. Trying every edit sequence is hopeless. The same two-prefix state as LCS turns it into a table fill with three moves per cell.

We'll go from simple to more technical:

  1. The pattern: dp[i][j] from the diagonal (replace or match), above (delete) and left (insert).
  2. A second flavor: operations with different costs.
  3. Where edit distance runs in production (spell check, fuzzy search, record matching).
  4. What breaks it: base cases, a missing move, and unscaled costs.

The approach

Java

int minDistance(String a, String b) {
    int m = a.length(), n = b.length();
    int[][] dp = new int[m + 1][n + 1];                // dp[i][j] = edits to turn a[0..i) into b[0..j)
    for (int i = 0; i <= m; i++) dp[i][0] = i;         // delete all i characters
    for (int j = 0; j <= n; j++) dp[0][j] = j;         // insert all j characters
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (a.charAt(i - 1) == b.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];                             // match: no edit needed
            } else {
                dp[i][j] = 1 + Math.min(dp[i - 1][j - 1],                // replace a's char with b's
                               Math.min(dp[i - 1][j],                    // delete a's char
                                        dp[i][j - 1]));                  // insert b's char
            }
        }
    }
    return dp[m][n];
}

Python

def min_distance(a: str, b: str) -> int:
    m, n = len(a), len(b)
    dp = [[0] * (n + 1) for _ in range(m + 1)]         # dp[i][j] = edits to turn a[:i] into b[:j]
    for i in range(m + 1):
        dp[i][0] = i                                   # delete all i characters
    for j in range(n + 1):
        dp[0][j] = j                                   # insert all j characters
    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]            # match: no edit needed
            else:
                dp[i][j] = 1 + min(dp[i - 1][j - 1],   # replace a's char with b's
                                   dp[i - 1][j],       # delete a's char
                                   dp[i][j - 1])       # insert b's char
    return dp[m][n]

Look at the last characters. If they match, nothing needs to happen to them. If not, the last edit either replaced a's last character (diagonal), deleted it (from above), or inserted b's last character (from the left) - take the cheapest. The base row and column are the only way to turn something into an empty string (all deletes) or build from nothing (all inserts). "robot" to "boat" is 3; "kitten" to "sitting" is 3.

A second flavor: operations with different costs

Real systems often weigh operations differently - a replacement might cost more than a delete-plus-insert, or insertions might be cheap. The recurrence doesn't change shape; each move just carries its own cost, and so do the base cases:

Java

for (int i = 1; i <= m; i++) dp[i][0] = i * deleteCost;
for (int j = 1; j <= n; j++) dp[0][j] = j * insertCost;
// cell: min(dp[i-1][j-1] + (match ? 0 : replaceCost), dp[i-1][j] + deleteCost, dp[i][j-1] + insertCost)

Python

for i in range(1, m + 1):
    dp[i][0] = i * delete_cost
for j in range(1, n + 1):
    dp[0][j] = j * insert_cost
# cell: min(dp[i-1][j-1] + (0 if match else replace_cost), dp[i-1][j] + delete_cost, dp[i][j-1] + insert_cost)

With insert = delete = 1 and replace = 5, "abc" to "adc" costs 2 (delete b, insert d) rather than 5 - the DP finds that automatically. Forgetting to scale the base cases (writing dp[i][0] = i) is the classic slip. With only insertions and deletions allowed, the distance is m + n - 2 * LCS.

Where you'll see it

Spell-checkers rank suggestions by edit distance, search engines match queries with typos, record-linkage systems match names across databases ("Jon Smyth" vs "John Smith"), and genome aligners score alignments with weighted versions of this exact table (substitution matrices and gap penalties).

Which tool for which job?

You needUse
The distancetwo-row DP, O(m × n) time
Is it at most k?a band of width 2k + 1 around the diagonal
The actual editsthe full table, traced back
Typos that swap two lettersDamerau-Levenshtein (adds a transposition move)
Nearest words in a dictionarya BK-tree, or a trie walked with a DP row
Insert and delete onlym + n - 2 × LCS

Common mistakes

MistakeWhat happensDo this instead
Row and column 0 left at 0"abc" to "" costs nothingdp[i][0] = i, dp[0][j] = j
No replace move"abc" to "abd" costs 2 instead of 1three moves per cell
Weighted costs, unweighted base caseswrong totalsscale the base cases by the delete and insert costs
Full table for a yes/no within k25,000,000 cells instead of 34,988a diagonal band
Treating a swap as one edit with plain Levenshtein"teh" to "the" costs 2add the transposition move

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. What do the three neighbors of dp[i][j] represent?

Show answer

dp[i - 1][j - 1]: the last characters are aligned - free if they match, one replace if not. dp[i - 1][j]: a's last character is deleted. dp[i][j - 1]: b's last character is inserted. The cell takes the cheapest.

2. Why is dp[i][0] = i rather than 0?

Show answer

Turning the first i characters of a into an empty string takes exactly i deletions - there's no other way. Leaving it at 0 makes deletions free and understates every distance built on it.

3. With insert = delete = 1 and replace = 5, what does "abc" to "adc" cost?

Show answer

2: delete 'b' and insert 'd' (1 + 1) beats replacing 'b' with 'd' (5). The weighted recurrence compares all three moves at every cell, so it picks the delete-plus-insert route on its own.

Key takeaways

  • dp[i][j] = cost to turn a's first i characters into b's first j.
  • A match copies the diagonal; otherwise take 1 + the cheapest of replace (diagonal), delete (above) and insert (left).
  • Base cases: i deletes down the first column, j inserts along the first row.
  • Different operation costs keep the same shape - just weight the moves and the base cases.
  • O(m * n) time, O(min(m, n)) space for the distance alone.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Distancetwo-row DP
Within k?diagonal band
The editsfull table, traced back
Swapped lettersadd the transposition move
Dictionary suggestionsBK-tree or trie plus DP rows

Snippets worth knowing by heart

Java

for (int i = 1; i <= m; i++) {
    cur[0] = i;
    for (int j = 1; j <= n; j++)
        cur[j] = Math.min(prev[j - 1] + (a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1),
                          Math.min(prev[j], cur[j - 1]) + 1);
    int[] t = prev; prev = cur; cur = t;
}

Python

if i > 1 and j > 1 and a[i - 1] == b[j - 2] and a[i - 2] == b[j - 1]:
    d[i][j] = min(d[i][j], d[i - 2][j - 2] + 1)      # adjacent transposition

The traps

  • Zero base cases: "abc" to "" costs nothing. Set dp[i][0] = i and dp[0][j] = j.
  • No replace move: "abc" to "abd" costs 2 instead of 1. Take the min of three moves per cell.
  • Unscaled weighted base cases: totals come out wrong. Scale the base row and column by the delete and insert costs.
  • A full table for a threshold check: millions of cells for a yes/no. Fill only a diagonal band of width k.

Staff-level points, one line each

  • 5,000-char strings, 3 edits apart: 25,000,000 cells for the full DP, 34,988 for a k = 3 band.
  • "teh" → "the": 2 with Levenshtein, 1 with the transposition move.
  • A traced-back script turned a into b in exactly distance-many edits on 300 pairs.
  • Edit distance is a metric (2,000 triples checked). A BK-tree took 331 comparisons instead of 4,869.

Say it in an interview

  • "dp[i][j] takes the best of replace or match, delete, and insert."
  • "If I only need 'within k', I fill a band around the diagonal."
  • "For spell suggestions I'd use a BK-tree or a trie with DP rows."
  • "Transpositions need the Damerau move."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Edit Distance free

Edit Distance With Costs

MediumProCoding problemJava · Python

Turn string a into string b using single-character operations, each with its own cost: inserting a character costs insertCost, deleting one costs deleteCost, and replacing one costs replaceCost. Return the minimum total cost.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Edit Distance

HardProCoding problemJava · Python

You can change a string one character at a time: insert a character, delete one, or replace one with another. Return the fewest changes that turn word1 into word2.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Edit Distance free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime