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
| Variant | Allowed operations | Note |
|---|---|---|
| Levenshtein | insert, delete, replace | the default |
| LCS distance | insert, delete only | |
| Hamming | replace only | requires equal lengths |
| Damerau-Levenshtein | + transpose adjacent | add a fourth branch reading dp[i-2][j-2] |
| Weighted | different costs per operation | replace the 1 + with the cost |
| Alphabet-dependent costs | cost depends on the characters | cost[a[i]][b[j]] |
| Affine gaps | opening a run of gaps costs more than extending | 3 layers: match / gap-in-A / gap-in-B (Gotoh) |
| Local alignment | best matching substring pair | Smith-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
| Situation | Method | Time |
|---|---|---|
| Answer is known | band the DP to | |
| Need the alignment, memory-bound | Hirschberg | time, space |
| Small alphabet, raw speed | Myers’ bit-vector algorithm | |
| Approximate matching in a text | Bitap | |
| Very similar strings | Ukkonen’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