- Recursive Approach
- Recursive with Memoization
- Dynamic Programming Approach
- Dynamic Programming with Space Optimization
The edit distance problem is essentially finding the minimum number of operations required to transform one string into another. The possible operations are insertion, deletion, and substitution. Using a recursive approach, we can compare the last characters of both strings and decide on the minimum operation needed. By recursively solving subproblems, we can handle all possible transformations.
- Compare the last characters of the two strings.
- If they are the same, move both pointers backward.
- If they are different, consider all operations:
- Insert a character.
- Delete a character.
- Substitute a character.
- Recur for all three operations and choose the one with the minimal cost.
def minDistanceRecursive(word1, word2):
def helper(i, j):
# Base cases: if one of the strings is empty
if i == 0:
return j
if j == 0:
return i
# If characters are the same, no operation needed
if word1[i-1] == word2[j-1]:
return helper(i-1, j-1)
# If characters are different, consider all operations
insert_op = helper(i, j-1) # insert
delete_op = helper(i-1, j) # delete
replace_op = helper(i-1, j-1) # replace
return 1 + min(insert_op, delete_op, replace_op)
return helper(len(word1), len(word2))
# Time Complexity: O(3^(m+n)) - exponential time complexity
# Space Complexity: O(m+n) - stack space for recursionThe recursive approach recalculates the same subproblems multiple times. By storing the results of already solved subproblems using memoization, we can significantly reduce the time complexity.
- Similar to the recursive approach, but create a dictionary to store the results of subproblems.
- Before calculating the edit distance for a pair of indices, check the dictionary to see if it has already been solved.
def minDistanceRecursiveMemo(word1, word2):
memo = {}
def helper(i, j):
if (i, j) in memo:
return memo[(i, j)]
if i == 0:
return j # need j insertions
if j == 0:
return i # need i deletions
if word1[i-1] == word2[j-1]:
memo[(i, j)] = helper(i-1, j-1)
else:
insert_op = helper(i, j-1)
delete_op = helper(i-1, j)
replace_op = helper(i-1, j-1)
memo[(i, j)] = 1 + min(insert_op, delete_op, replace_op)
return memo[(i, j)]
return helper(len(word1), len(word2))
# Time Complexity: O(m * n) - where m and n are the lengths of the strings
# Space Complexity: O(m * n) - for memoization storageThe dynamic programming approach uses a 2D array where each element dp[i][j] represents the minimum edit distance for the strings word1[:i] and word2[:j]. This avoids recalculations by using previously computed values.
- Initialize a 2D array
dpwith dimensions(m+1) x (n+1). - Base Case Initialization:
- Transforming an empty string to
word2requiresjinsertions. - Transforming
word1to an empty string requiresideletions.
- Transforming an empty string to
- Fill the array based on previously discussed recursive relation over all
iandj. - The value at
dp[m][n]gives the edit distance for the entire strings.
def minDistanceDP(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Initialize base cases
for i in range(m + 1):
dp[i][0] = i # delete all characters in word1
for j in range(n + 1):
dp[0][j] = j # insert all characters in word2
# Fill the table
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] # No change needed
else:
insert_op = dp[i][j - 1]
delete_op = dp[i - 1][j]
replace_op = dp[i - 1][j - 1]
dp[i][j] = 1 + min(insert_op, delete_op, replace_op)
return dp[m][n]
# Time Complexity: O(m * n) - double iteration over the strings
# Space Complexity: O(m * n) - 2D dp array usageSince the current row in the DP table is dependent only on the previous row, we can reduce the space complexity to O(n) by keeping track of only two rows: current and previous.
- Use two arrays,
previousandcurrent, to keep track of the edit distances. - At each iteration, update the
currentbased onpreviousand reset after each row. - Finally, return the last element which represents the edit distance.
def minDistanceDPOptimized(word1, word2):
m, n = len(word1), len(word2)
# Initialize two rows for the current and previous computations
previous = [0] * (n + 1)
current = [0] * (n + 1)
# Initializing the base case
for j in range(n + 1):
previous[j] = j
# Fill the array
for i in range(1, m + 1):
current[0] = i # Base case: transforming word1 to empty word2
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
current[j] = previous[j - 1]
else:
insert_op = current[j - 1]
delete_op = previous[j]
replace_op = previous[j - 1]
current[j] = 1 + min(insert_op, delete_op, replace_op)
previous, current = current, previous
return previous[n]
# Time Complexity: O(m * n) - double iteration over the length of the strings
# Space Complexity: O(n) - We use two arrays to store intermediate resultsChoose the approach that best fits your constraints based on time and space trade-offs. The space-optimized dynamic programming solution is typically the most efficient in practice.