You are given two strings word1 and word2, each consisting of lowercase English letters.
You are allowed to perform three operations on word1 an unlimited number of times:
Return the minimum number of operations to make word1 equal word2.
Example 1:
Input: word1 = "monkeys", word2 = "money"
Output: 2Explanation:monkeys -> monkey (remove s)monkey -> money (remove k)
Example 2:
Input: word1 = "neatcdee", word2 = "neetcode"
Output: 3Explanation:neatcdee -> neetcdee (replace a with e)neetcdee -> neetcde (remove last e)neetcde -> neetcode (insert o)
Constraints:
0 <= word1.length, word2.length <= 100word1 and word2 consist of lowercase English letters.
You should aim for a solution as good or better than O(m * n) time and O(m * n) space, where m and n are the lengths of the strings word1 and word2, respectively.
Try to think in terms of recursion and visualize it as a decision tree, as we have choices at each recursion step. Can you determine the recurrence relation and base cases?
We recursively iterate through the strings using indices i and j for word1 and word2, respectively. If the characters at the current indices match, we increment both indices without counting an operation. Otherwise, we have three choices: insert the character at the current index of word1 (increment j), delete the current character of word1 (increment i), or replace the character at index i in word1 (increment both i and j).
If index i goes out of bounds, we return the number of remaining characters in word2 (using insert operations). If index j goes out of bounds, we return the number of remaining characters in word1 (using delete operations). At each step, we return the minimum operation path. This approach is exponential. Can you think of a way to optimize it?
We can use memoization to cache the results and avoid redundant calculations. A hash map or a 2D array can be used to store these results.
This problem asks for the minimum number of operations required to convert word1 into word2.
The allowed operations are:
At any position in both strings, we compare characters at indices i and j.
The recursive function represents:
"What is the minimum number of operations needed to convert word1[i:] into word2[j:]?"
If the characters already match, we simply move forward in both strings without using any operation.
If they do not match, we try all three possible operations and take the minimum cost.
m = len(word1) and n = len(word2).dfs(i, j):i is the current index in word1j is the current index in word2word1:word2 must be insertedn - jword2:word1 must be deletedm - iword1[i] == word2[j]:dfs(i + 1, j + 1)word1: dfs(i + 1, j)word1: dfs(i, j + 1)dfs(i + 1, j + 1)1 for the current operation(0, 0) and return the final resultclass Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
def dfs(i, j):
if i == m:
return n - j
if j == n:
return m - i
if word1[i] == word2[j]:
return dfs(i + 1, j + 1)
res = min(dfs(i + 1, j), dfs(i, j + 1))
res = min(res, dfs(i + 1, j + 1))
return res + 1
return dfs(0, 0)Where is the length of and is the length of .
This problem asks for the minimum number of edit operations required to convert word1 into word2.
The allowed operations are:
The recursive solution explores all possibilities, but many subproblems repeat. To optimize this, we use top-down dynamic programming (memoization).
A state is uniquely defined by:
i: current index in word1j: current index in word2The recursive function answers:
"What is the minimum number of operations needed to convert word1[i:] into word2[j:]?"
By caching results for each (i, j) pair, we avoid recomputing the same states.
m = len(word1) and n = len(word2).dp where:dp[(i, j)] stores the minimum edit distance for word1[i:] and word2[j:]dfs(i, j):i is the current index in word1j is the current index in word2i reaches the end of word1:word2 (n - j)j reaches the end of word2:word1 (m - i)(i, j) is already in dp:word1[i] == word2[j]:dfs(i + 1, j + 1)word1 → dfs(i + 1, j)word1 → dfs(i, j + 1)dfs(i + 1, j + 1)1, and store it in dp[(i, j)](0, 0) and return the final answerclass Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
dp = {}
def dfs(i, j):
if i == m:
return n - j
if j == n:
return m - i
if (i, j) in dp:
return dp[(i, j)]
if word1[i] == word2[j]:
dp[(i, j)] = dfs(i + 1, j + 1)
else:
res = min(dfs(i + 1, j), dfs(i, j + 1))
res = min(res, dfs(i + 1, j + 1))
dp[(i, j)] = res + 1
return dp[(i, j)]
return dfs(0, 0)Where is the length of and is the length of .
We want the minimum number of edits needed to convert word1 into word2, where an edit can be:
Instead of using recursion, we can solve this using bottom-up dynamic programming by building the answer for smaller suffixes first.
We define a DP state that answers:
"What is the minimum edit distance between word1[i:] and word2[j:]?"
By filling a table from the end of the strings toward the beginning, every subproblem we need is already solved when we reach it.
dp of size(len(word1) + 1) × (len(word2) + 1).dp[i][j] represent the minimum number of operations to convertword1[i:] into word2[j:].word1 is exhausted (i == len(word1)), all remaining characters of word2 must be inserted:dp[len(word1)][j] = len(word2) - jword2 is exhausted (j == len(word2)), all remaining characters of word1 must be deleted:dp[i][len(word2)] = len(word1) - i(i, j):word1[i] == word2[j]:dp[i][j] = dp[i + 1][j + 1]dp[i + 1][j]dp[i][j + 1]dp[i + 1][j + 1]1dp[0][0]dp[0][0]class Solution:
def minDistance(self, word1: str, word2: str) -> int:
dp = [[float("inf")] * (len(word2) + 1) for i in range(len(word1) + 1)]
for j in range(len(word2) + 1):
dp[len(word1)][j] = len(word2) - j
for i in range(len(word1) + 1):
dp[i][len(word2)] = len(word1) - i
for i in range(len(word1) - 1, -1, -1):
for j in range(len(word2) - 1, -1, -1):
if word1[i] == word2[j]:
dp[i][j] = dp[i + 1][j + 1]
else:
dp[i][j] = 1 + min(dp[i + 1][j], dp[i][j + 1], dp[i + 1][j + 1])
return dp[0][0]Where is the length of and is the length of .
We want the minimum number of edits (insert, delete, replace) to convert word1 into word2.
In the 2D DP solution, we used dp[i][j] to represent the answer for word1[i:] and word2[j:].
But notice that each cell dp[i][j] depends only on:
dp[i + 1][j] (delete)dp[i][j + 1] (insert)dp[i + 1][j + 1] (replace / match)So when filling the table from bottom to top, we only need:
i + 1) andi)That means we can optimize space by keeping just two 1D arrays:
dp for the next rownextDp for the current rowTo reduce memory even more, we also ensure the 1D arrays are based on the shorter string (swap if needed).
m = len(word1) and n = len(word2).word2 is longer than word1, swap them so that n is the smaller length (this keeps the DP arrays small).n + 1:dp represents the DP values for row i + 1nextDp represents the DP values for row iword1 is exhausted:dp[j] = n - j for all jword1 is empty, we must insert the remaining characters of word2i from m - 1 down to 0:nextDp[n] = m - iword2 is exhausted, we must delete the remaining characters of word1i, iterate j from n - 1 down to 0:word1[i] == word2[j]:nextDp[j] = dp[j + 1]1 + minimum of:dp[j]nextDp[j + 1]dp[j + 1]nextDp into dp for the next iteration.dp[0], which represents converting word1[0:] to word2[0:].dp[0]class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
if m < n:
m, n = n, m
word1, word2 = word2, word1
dp = [0] * (n + 1)
nextDp = [0] * (n + 1)
for j in range(n + 1):
dp[j] = n - j
for i in range(m - 1, -1, -1):
nextDp[n] = m - i
for j in range(n - 1, -1, -1):
if word1[i] == word2[j]:
nextDp[j] = dp[j + 1]
else:
nextDp[j] = 1 + min(dp[j], nextDp[j + 1], dp[j + 1])
dp = nextDp[:]
return dp[0]Where is the length of and is the length of .
We want the minimum number of edits (insert, delete, replace) needed to convert word1 into word2.
The classic DP idea is:
dp[i][j] = minimum operations to convert word1[i:] into word2[j:]But to compute dp[i][j], we only need three neighboring states:
dp[i + 1][j] (delete from word1)dp[i][j + 1] (insert into word1)dp[i + 1][j + 1] (replace, or match if characters are equal)That means we don't need the full 2D table. We can compress it into a single 1D array dp, and update it row-by-row (from the end of the strings to the start).
The tricky part of in-place updates is that dp[i + 1][j + 1] (the diagonal value) would get overwritten.
So we carry that diagonal value using one extra variable (nextDp), and another temporary variable to shift it correctly while moving left.
We also swap the strings if needed so the DP array is based on the shorter word, keeping memory minimal.
m = len(word1) and n = len(word2).word2 is longer than word1, swap them so n is the smaller length (smaller DP array).dp of size n + 1:word1 is exhausted:dp[j] = n - j (we must insert the remaining characters of word2)i from m - 1 down to 0:nextDp (this represents dp[i + 1][j + 1] during updates)dp[n] = m - i (when word2 is exhausted, we must delete remaining characters of word1)j from n - 1 down to 0:dp[j] in temp before overwriting it (this becomes the next diagonal)word1[i] == word2[j]:dp[j] = nextDp1 + minimum of:dp[j] (still represents dp[i + 1][j])dp[j + 1] (already updated for current row)nextDp (the diagonal dp[i + 1][j + 1])nextDp = tempdp[0] represents converting the full word1 into the full word2.dp[0]class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
if m < n:
m, n = n, m
word1, word2 = word2, word1
dp = [n - i for i in range(n + 1)]
for i in range(m - 1, -1, -1):
nextDp = dp[n]
dp[n] = m - i
for j in range(n - 1, -1, -1):
temp = dp[j]
if word1[i] == word2[j]:
dp[j] = nextDp
else:
dp[j] = 1 + min(dp[j], dp[j + 1], nextDp)
nextDp = temp
return dp[0]Where is the length of and is the length of .
When converting word1 to word2, an insert into word1 is equivalent to advancing j (moving forward in word2), while a delete from word1 advances i. Mixing these up leads to incorrect recurrence relations. Remember: insert adds a character to match word2[j], delete removes word1[i].
The base cases require returning the number of remaining characters when one string is exhausted. A common mistake is returning 0 or forgetting to handle when i == m (return n - j) or j == n (return m - i). These represent the insertions or deletions needed to complete the transformation.
When characters don't match, you must add 1 to the minimum of the three recursive calls to account for the current operation. Forgetting this addition results in an answer that's always too small, as it doesn't count the edit being performed at the current position.
The DP table needs dimensions (m + 1) x (n + 1) to accommodate the base cases where either string is empty. Using m x n causes index out of bounds errors when accessing dp[m][j] or dp[i][n] for the boundary conditions.
When word1[i] == word2[j], no operation is needed and you should directly use dp[i+1][j+1] without adding 1. A common mistake is still adding 1 or considering all three operations when characters match, leading to an inflated edit distance.