Edit Distance (Levenshtein Distance)
Edit Distance is a classic dynamic programming problem that measures the minimum number of operations required to transform one string into another.
The allowed operations are:
- Inserting a character
- Deleting a character
- Replacing a character
Each operation has a cost of 1.
Problem Definition
Given two strings:
s– the source stringt– the target string
Determine the minimum number of operations required
to transform string s into string t.
Example:
s = "kitten"t = "sitting"
Result: 3 (replacement, replacement, insertion)
For example, `"kitten"` can be transformed into `"sitting"` in three operations: 1. Replace `k` with `s`: `"sitten"`. 2. Replace `e` with `i`: `"sittin"`. 3. Insert `g` at the end: `"sitting"`. Therefore, the edit distance is 3.
Note: Standard Levenshtein distance does not treat swapping two adjacent characters as a single operation. If adjacent transpositions are allowed as one operation, a related variant is the Damerau–Levenshtein distance.
Why Dynamic Programming?
When we consider prefixes of the strings, the problem naturally breaks down into smaller subproblems.
Edit Distance has:
- Optimal substructure
- Overlapping subproblems
This makes it an ideal problem for dynamic programming.
DP State
We define:
dp[i][j] = the minimum number of operations
required to transform the first i characters of string s
into the first j characters of string t
Base Cases
dp[0][j] = j(insert j characters)dp[i][0] = i(delete i characters)
Transition (DP Recurrence)
If the last characters are equal:
dp[i][j] = dp[i-1][j-1]
If they are different:
dp[i][j] = 1 + min(
dp[i-1][j], // deletion
dp[i][j-1], // insertion
dp[i-1][j-1] // replacement
)
Implementation (C++)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int min3(int a, int b, int c) {
return min(a, min(b, c));
}
int main() {
string source, target;
cin >> source >> target;
int n = source.size();
int m = target.size();
vector<vector<int>> dp(
n + 1, vector<int>(m + 1, 0)
);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} else if (source[i - 1] == target[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + min3(
dp[i - 1][j], // deletion
dp[i][j - 1], // insertion
dp[i - 1][j - 1] // replacement
);
}
}
}
cout << "Edit distance: " << dp[n][m];
return 0;
}
Example Input and Output
Input:
kitten sitting
Output:
Edit distance: 3
The minimum edit distance is 3. We can transform kitten
into sitting by performing two replacements and one insertion.
Time and Space Complexity
- Time complexity: O(n · m)
- Space complexity: O(n · m)
where n and m are the lengths of the strings.
Advanced Note: Space Optimization
The standard implementation stores the entire DP table and therefore requires O(n · m) space. However, each cell depends only on the previous row and the current row. This means we can reduce the space complexity to O(min(n, m)) by maintaining only two rows.
The time complexity remains O(n · m), but the memory usage can be significantly reduced for long strings. This optimization is useful when the edit distance is needed but the complete DP table is not required.
Space-Optimized Implementation (Two DP Arrays)
The standard Edit Distance algorithm stores the entire DP table, which requires O(n · m) space. However, each state depends only on the previous row and the already computed values in the current row. Therefore, we can optimize the memory usage by maintaining only two one-dimensional DP arrays.
The previous array stores the results from the previous
row, while the current array stores the results for
the row currently being calculated. After each row is completed,
the two arrays are swapped.
To achieve O(min(n, m)) space complexity, we make
the shorter string the second string, so that the DP arrays have
only m + 1 elements, where m is the length
of the shorter string.
Implementation (C++)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int min3(int a, int b, int c) {
return min(a, min(b, c));
}
int main() {
string source, target;
cin >> source >> target;
// Keep target as the shorter string
if (target.size() > source.size()) {
swap(source, target);
}
int n = static_cast<int>(source.size());
int m = static_cast<int>(target.size());
vector<int> previous(m + 1);
vector<int> current(m + 1);
// Base case: converting an empty string into a prefix of target
for (int j = 0; j <= m; j++) {
previous[j] = j;
}
for (int i = 1; i <= n; i++) {
// Convert a prefix of source into an empty string
current[0] = i;
for (int j = 1; j <= m; j++) {
if (source[i - 1] == target[j - 1]) {
current[j] = previous[j - 1];
} else {
current[j] = 1 + min3(
previous[j], // deletion
current[j - 1], // insertion
previous[j - 1] // replacement
);
}
}
// The current row becomes the previous row
swap(previous, current);
}
cout << "Edit distance: " << previous[m] << '\n';
return 0;
}
Example Input and Output
Input:
kitten sitting
Output:
Edit distance: 3
Complexity Analysis
- Time Complexity: O(n · m), because each DP state is calculated once.
- Space Complexity: O(min(n, m)), because only two arrays of length min(n, m) + 1 are stored.
This optimization is useful when working with long strings and when only the final edit distance is required. If the complete DP table is needed to reconstruct the actual sequence of edits, the standard two-dimensional implementation is more convenient.
Practical Applications
Edit Distance is used in:
- Spell checking
- Approximate string matching and fuzzy search
- Bioinformatics (DNA sequences)
- Natural language processing
Connections to Other DP Problems
Edit Distance is related to:
- Longest Common Subsequence (LCS)
- Grid DP problems
- String alignment problems
Understanding this problem is an important step toward mastering more advanced dynamic programming techniques.
|
Previous
|< DP: Grid Dynamic Programming |
Next
Algorithm Complexity: Replacing Loops with a Formula >| |
