~25 minutes · Day 69 · two-string DP · Python
Tonight's interview pattern is what lets a spellchecker, diff tool, or search box ask: “how far apart are these two words?” The LeetCode version is Edit Distance: find the fewest insert, delete, or replace operations needed to turn one word into another. The classic name is Levenshtein distance, the minimum number of one-character edits between two strings (Wikipedia).
Memorize this linedp[i][j] = edits to turn one prefix into the other: free when characters match, else 1 + min(insert, delete, replace).
That one sentence is the whole solution. The table cell is not about the whole words yet. It is about two prefixes: word1[:i] and word2[:j]. Small prefixes build larger prefixes.
Put prefixes of the first word down the side and prefixes of the second word across the top. Each cell only needs three earlier cells: left, up, and diagonal.
The brute force idea is honest: compare the last characters of the two remaining prefixes. If they match, move diagonally for free. If they do not, try all three edits and take the cheapest result. That recursion is exponential, up to O(3m+n), because the same prefix pair gets recomputed many times.
def slow(i, j):
if i == 0: return j # insert j chars
if j == 0: return i # delete i chars
if word1[i - 1] == word2[j - 1]:
return slow(i - 1, j - 1) # matching char is free
return 1 + min(
slow(i, j - 1), # insert
slow(i - 1, j), # delete
slow(i - 1, j - 1) # replace
)
The fix is not a new idea. Keep the same meaning, but store each prefix answer once.
LeetCode 72 asks for the minimum number of insertions, deletions, and replacements (problem statement). The base row and base column are the cost of converting a word into the empty string, or the empty string into a word.
def min_distance(a, b):
m, n = len(a), len(b)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1): dp[i][0] = i # delete all i chars
for j in range(n + 1): dp[0][j] = j # insert all j chars
for i in range(1, m + 1):
for j in range(1, n + 1):
if a[i - 1] == b[j - 1]:
dp[i][j] = dp[i - 1][j - 1] # match is free
else:
dp[i][j] = 1 + min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1])
return dp[m][n]
Read the mismatch line slowly: left means insert, up means delete, diagonal means replace. They are all one edit plus a smaller solved prefix pair.
Delete Operation for Two Strings: find the minimum deletions needed to make two strings equal.
Before opening the reveal: keep the same state, dp[i][j]. If the current characters match, what should happen? If they do not match, which moves are still legal?
Same state: dp[i][j] is the minimum deletions to make word1[:i] and word2[:j] equal. Match means copy the diagonal. Mismatch means delete one current character from either side: 1 + min(dp[i - 1][j], dp[i][j - 1]). The replace move disappears.
| Approach | Time | Space |
|---|---|---|
| Brute force recursion | O(3m+n) | O(m+n) |
| 2-D DP table | O(mn) | O(mn) |
The DP table is the price of not forgetting. Every prefix pair is solved once, then reused.
1. What does dp[i][j] mean here?
word1[:i] and word2[:j]. That prefix meaning is what makes the base row, base column, and transition line up.2. If the two current characters match, which value can you copy?
dp[i - 1][j - 1].3. Spot the bug in this match case:
if a[i - 1] == b[j - 1]:
dp[i][j] = 0
4. Which new problem has the same two-string DP shape?
Solve Edit Distance in your Notion tracker with the cycle - predict → attempt → compare → encode:
Watch NeetCode - Edit Distance after your attempt, as the compare step. Do not start there; use it to repair your model.
Stuck or curious? Ask your teacher (the agent) - that's what it's for.