Edit Distance Algorithm
1. Algorithm Explanation
The Edit Distance (or Levenshtein distance) between two strings
s
of length n
and t
of length m
is the minimum number of single-character insertions, deletions, or substitutions
required to transform s
into t
.
We define a DP table dp[i][j]
= edit distance between
s[0..i-1]
and t[0..j-1]
. The recurrence is:
-
dp[0][0] = 0
(empty → empty costs 0) -
dp[i][0] = i
(delete all i characters) -
dp[0][j] = j
(insert all j characters) -
For
i>0, j>0
:dp[i][j] = min( dp[i-1][j] + 1, // deletion dp[i][j-1] + 1, // insertion dp[i-1][j-1] + cost // substitution (cost = 0 if s[i-1]==t[j-1], else 1) )
Time Complexity
Filling an (n+1)×(m+1)
table takes O(n·m)
time.
Memory Complexity
Naïvely, we store the full table in O(n·m)
space.
It can be reduced to O(min(n,m))
by keeping only two rows at once.
2. Easy Problems
2.1 Problem: LeetCode 72 – Edit Distance
Compute the minimum edit distance between two given strings.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int minDistance(const std::string &s, const std::string &t) {
int n = s.size(), m = t.size();
std::vector<std::vector<int>> dp(n+1, std::vector<int>(m+1));
for (int i = 0; i <= n; ++i) dp[i][0] = i;
for (int j = 0; j <= m; ++j) dp[0][j] = j;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int cost = (s[i-1] == t[j-1] ? 0 : 1);
dp[i][j] = std::min({
dp[i-1][j] + 1,
dp[i][j-1] + 1,
dp[i-1][j-1] + cost
});
}
}
return dp[n][m];
}
int main() {
std::cout << minDistance("kitten", "sitting") << "\n"; // Outputs 3
return 0;
}
2.2 Problem: LeetCode 161 – One Edit Distance
Check if two strings are exactly one edit apart.
#include <iostream>
#include <string>
#include <cmath>
bool isOneEditDistance(const std::string &s, const std::string &t) {
int n = s.size(), m = t.size();
if (std::abs(n - m) > 1) return false;
int i = 0, j = 0, edits = 0;
while (i < n && j < m) {
if (s[i] == t[j]) {
++i; ++j;
} else {
if (++edits > 1) return false;
if (n > m) ++i;
else if (n < m) ++j;
else { ++i; ++j; }
}
}
edits += (i < n) + (j < m);
return edits == 1;
}
int main() {
std::cout << std::boolalpha
<< isOneEditDistance("abc", "ab") << "\n"; // true
return 0;
}
3. Intermediate Problems
3.1 Problem: LeetCode 583 – Delete Operation for Two Strings
Return the minimum number of delete operations on the two strings to make them equal.
This equals n + m – 2·LCS(s, t)
, but can be solved by edit‐distance DP restricted to deletions only.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int minDistanceDelete(const std::string &s, const std::string &t) {
int n = s.size(), m = t.size();
std::vector<std::vector<int>> dp(n+1, std::vector<int>(m+1));
for (int i = 0; i <= n; ++i) dp[i][0] = i;
for (int j = 0; j <= m; ++j) dp[0][j] = j;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i-1] == t[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = 1 + std::min(dp[i-1][j], dp[i][j-1]);
}
}
return dp[n][m];
}
int main() {
std::cout << minDistanceDelete("sea", "eat") << "\n"; // Outputs 2
return 0;
}
3.2 Problem: LeetCode 1312 – Minimum Insertion Steps to Make a String Palindrome
Find the minimum number of insertions to make s
a palindrome.
Equivalent to n – LPS(s)
, but solvable by DP on edit distance where only insertions are allowed.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int minInsertionsPalindrome(const std::string &s) {
int n = s.size();
std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
for (int len = 2; len <= n; ++len) {
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
if (s[i] == s[j])
dp[i][j] = dp[i+1][j-1];
else
dp[i][j] = 1 + std::min(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}
int main() {
std::cout << minInsertionsPalindrome("leetcode") << "\n"; // Outputs 5
return 0;
}
4. Hard Problem
4.1 Problem: Damerau–Levenshtein Distance (with transpositions)
Compute the edit distance allowing transposition of two adjacent characters as one operation. This generalizes Levenshtein by adding one more case.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <limits>
int damerauLevenshtein(const std::string &s, const std::string &t) {
int n = s.size(), m = t.size();
const int INF = n + m;
std::vector<std::vector<int>> dp(n+2, std::vector<int>(m+2, INF));
dp[0][0] = INF;
for (int i = 0; i <= n; ++i) {
dp[i+1][1] = i;
dp[i+1][0] = INF;
}
for (int j = 0; j <= m; ++j) {
dp[1][j+1] = j;
dp[0][j+1] = INF;
}
std::vector<int> last(256, 0);
for (int i = 1; i <= n; ++i) {
int lastMatchCol = 0;
for (int j = 1; j <= m; ++j) {
int i1 = last[t[j-1]];
int j1 = lastMatchCol;
int cost = (s[i-1] == t[j-1]) ? 0 : 1;
if (cost == 0) lastMatchCol = j;
dp[i+1][j+1] = std::min({
dp[i][j] + cost, // substitution
dp[i+1][j] + 1, // insertion
dp[i][j+1] + 1, // deletion
dp[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1) // transposition
});
}
last[s[i-1]] = i;
}
return dp[n+1][m+1];
}
int main() {
std::cout << damerauLevenshtein("CA", "ABC") << "\n"; // Outputs 2
return 0;
}