Solving the Edit Distance Problem with Dynamic Programming
Problem Description
Edit Distance (Levenshtein distance) is a metric for measuring the similarity between two strings. It is defined as the minimum number of single-character edit operations required to transform string A into string B. Allowed operations include: inserting a character, deleting a character, and replacing a character. For example, the edit distance between "kitten" and "sitting" is 3 (replace k→s, replace e→i, insert g at the end).
Dynamic Programming Approach Analysis
- Problem Decomposition: Break down the transformation of long strings into subproblems. If A[0..i] and B[0..j] represent the first i characters of A and the first j characters of B respectively, then the edit distance dp[i][j] can be derived from shorter substrings.
- State Definition: Let dp[i][j] represent the minimum number of operations required to convert the first i characters of A into the first j characters of B.
- Boundary Cases:
- If A is empty (i=0), j characters need to be inserted, so dp[0][j] = j.
- If B is empty (j=0), i characters need to be deleted, so dp[i][0] = i.
- State Transition Equation:
- If A[i-1] == B[j-1] (the last characters are the same): No operation is needed, dp[i][j] = dp[i-1][j-1].
- Otherwise, take the minimum of the three operations plus 1:
- Replace: Replace A[i-1] with B[j-1], cost = dp[i-1][j-1] + 1.
- Delete: Delete A[i-1], cost = dp[i-1][j] + 1.
- Insert: Insert B[j-1] at the end of A, equivalent to deleting a character from B, cost = dp[i][j-1] + 1.
Formula:
\[ dp[i][j] = \begin{cases} dp[i-1][j-1] & \text{if } A[i-1] = B[j-1] \\ \min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 & \text{otherwise} \end{cases} \]
Example Derivation (A="kitten", B="sitting")
-
Initialize the dp table (row and column lengths are len(A)+1 and len(B)+1 respectively):
- First row: dp[0][j] = j (0,1,2,...,7)
- First column: dp[i][0] = i (0,1,2,...,6)
-
Calculate row by row (i=1..6, j=1..7):
- i=1, j=1: A[0]='k', B[0]='s', different. Compare:
- Replace: dp[0][0]+1 = 0+1 = 1
- Delete: dp[0][1]+1 = 1+1 = 2
- Insert: dp[1][0]+1 = 1+1 = 2
Take the minimum 1, so dp[1][1] = 1.
- Key subsequent steps:
- When i=6, j=7: A[5]='n', B[6]='g', different. Compare:
- Replace: dp[5][6]+1 = 2+1 = 3 (cost for "kitte"→"sittin" is 2, then replace 'n' with 'g')
- Delete: dp[5][7]+1 = 3+1 = 4
- Insert: dp[6][6]+1 = 3+1 = 4
Final result: dp[6][7] = 3.
- When i=6, j=7: A[5]='n', B[6]='g', different. Compare:
- i=1, j=1: A[0]='k', B[0]='s', different. Compare:
-
Result: dp[6][7] = 3 is the edit distance.
Optimizations and Extensions
- Space Optimization: In practice, only two arrays (rows) are needed for rolling storage, reducing space complexity from O(mn) to O(min(m,n)).
- Operation Recording: An auxiliary array can be used to record the operation types, allowing backtracking to output the specific sequence of edit steps.
- Application Scenarios: Spell checking, DNA sequence alignment, similarity calculation in natural language processing.
This method optimizes the exponential brute-force search to an O(mn) time complexity efficient algorithm using dynamic programming, exemplifying the core idea of trading space for time in dynamic programming.