The minimum number of single-character operations (insert, delete, replace) transforming one string into another. Also called Levenshtein distance.

The recurrence

The three branches are delete from , insert into , and replace.

int editDistance(const string& a, const string& b) {
    int n = a.size(), m = b.size();
    vector<int> prev(m + 1), cur(m + 1);
    for (int j = 0; j <= m; j++) prev[j] = j;
    for (int i = 1; i <= n; i++) {
        cur[0] = i;
        for (int j = 1; j <= m; j++)
            cur[j] = (a[i-1] == b[j-1]) ? prev[j-1]
                   : 1 + min({prev[j], cur[j-1], prev[j-1]});
        swap(prev, cur);
    }
    return prev[m];
}

time, space.

The variants

VariantAllowed operationsNote
Levenshteininsert, delete, replacethe default
LCS distanceinsert, delete only
Hammingreplace onlyrequires equal lengths
Damerau-Levenshtein+ transpose adjacentadd a fourth branch reading dp[i-2][j-2]
Weighteddifferent costs per operationreplace the 1 + with the cost
Alphabet-dependent costscost depends on the characterscost[a[i]][b[j]]
Affine gapsopening a run of gaps costs more than extending3 layers: match / gap-in-A / gap-in-B (Gotoh)
Local alignmentbest matching substring pairSmith-Waterman: clamp at 0, take the global max

Affine gap penalties matter in bioinformatics and occasionally in contests: a run of deletions costs rather than . Track three DP layers so the algorithm knows whether it is already inside a gap.

Faster than

SituationMethodTime
Answer is known band the DP to
Need the alignment, memory-boundHirschberg time, space
Small alphabet, raw speedMyers’ bit-vector algorithm
Approximate matching in a textBitap
Very similar stringsUkkonen’s where = distance

Like LCS, edit distance has no algorithm unless SETH fails — so the quadratic bound is not going to be improved in general.

Banding, concretely

When the problem guarantees the answer is at most , only the diagonal band can matter (each step off the diagonal costs at least 1):

for (int i = 1; i <= n; i++)
    for (int j = max(1, i - k); j <= min(m, i + k); j++)
        /* same recurrence, treating out-of-band cells as INF */;

— the difference between and when and .

Reconstructing the operations

Walk backwards from dp[n][m], at each step identifying which branch achieved the value:

while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && a[i-1] == b[j-1] && dp[i][j] == dp[i-1][j-1]) { i--; j--; }
    else if (i > 0 && j > 0 && dp[i][j] == dp[i-1][j-1] + 1) { ops.push_back("replace"); i--; j--; }
    else if (i > 0 && dp[i][j] == dp[i-1][j] + 1) { ops.push_back("delete"); i--; }
    else { ops.push_back("insert"); j--; }
}

Check the branches in a fixed order so the output is deterministic — many problems accept any optimal sequence, but some checkers are picky.

Applications

Spell checking, DNA sequence alignment, diff and version control, OCR post-correction, fuzzy search, plagiarism detection, and speech recognition scoring.

See also: LCS · Hirschberg · Bitap