Edit Distance Problem Levenshtein in DSA Python - Time & Space Complexity
We want to understand how the time needed to find the minimum edits between two words grows as the words get longer.
The question is: how does the work increase when the input words become bigger?
Analyze the time complexity of the following code snippet.
def levenshtein_distance(s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
for j in range(n + 1):
if i == 0:
dp[i][j] = j
elif j == 0:
dp[i][j] = i
elif s1[i - 1] == s2[j - 1]:
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[m][n]
This code calculates the minimum number of edits (insert, delete, replace) to change one word into another using a table.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops filling a 2D table of size (m+1) x (n+1).
- How many times: Outer loop runs m+1 times, inner loop runs n+1 times for each outer loop.
As the length of the two words grows, the number of operations grows roughly by multiplying their lengths.
| Input Size (m, n) | Approx. Operations |
|---|---|
| 10, 10 | ~100 |
| 100, 100 | ~10,000 |
| 1000, 1000 | ~1,000,000 |
Pattern observation: Doubling the length of both words roughly squares the work needed.
Time Complexity: O(m * n)
This means the time needed grows proportionally to the product of the lengths of the two words.
[X] Wrong: "The algorithm runs in linear time because it just compares characters."
[OK] Correct: The algorithm compares all pairs of prefixes, so it must fill a table with m times n cells, not just a single pass.
Understanding this time complexity helps you explain how dynamic programming solves problems efficiently by breaking them into smaller parts.
"What if we used recursion without memoization? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of Edit Distance
Edit Distance counts how many changes (insert, delete, replace) are needed to convert one string to another.Step 2: Compare options with definition
Only The minimum number of single-character edits to change one string into the other correctly describes this minimum number of single-character edits.Final Answer:
The minimum number of single-character edits to change one string into the other -> Option AQuick Check:
Edit Distance = minimum edits [OK]
- Confusing with longest common substring
- Thinking it counts common characters
- Assuming it measures string length
Solution
Step 1: Understand 2D list initialization in Python
Using list comprehension with separate inner lists avoids shared references.Step 2: Analyze each option
dp = [[0] * (n+1) for _ in range(m+1)] correctly creates independent rows. dp = [[0] * (m+1)] * (n+1) creates shallow copies causing bugs. Options A and C have wrong dimensions or types.Final Answer:
dp = [[0] * (n+1) for _ in range(m+1)] -> Option BQuick Check:
Proper 2D list init = dp = [[0] * (n+1) for _ in range(m+1)] [OK]
- Using multiplication for 2D lists causing shared references
- Mixing up m and n dimensions
- Creating 1D list instead of 2D
def levenshtein(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(m+1):
dp[i][0] = i
for j in range(n+1):
dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[j-1]:
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[m][n]
print(levenshtein('kitten', 'sitting'))Solution
Step 1: Understand the Levenshtein distance calculation
Compare 'kitten' and 'sitting' stepwise counting minimal edits: substitute 'k'->'s' (kitten -> sitten), substitute 'e'->'i' (sitten -> sittin), insert 'g' at end (sittin -> sitting). Total 3 edits.Step 2: Verify with code logic
The code correctly implements the DP approach and returns dp[m][n] which is 3 for these inputs.Final Answer:
3 -> Option CQuick Check:
Levenshtein('kitten','sitting') = 3 [OK]
- Counting only replacements, ignoring insertions
- Miscounting characters to change
- Confusing with substring length
def levenshtein_bug(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n+1)] * (m+1)
for i in range(m+1):
dp[i][0] = i
for j in range(n+1):
dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[j-1]:
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[m][n]Solution
Step 1: Analyze dp initialization
Using multiplication to create dp causes all rows to point to the same list, so updating one row updates all rows.Step 2: Understand impact on algorithm
This shared reference breaks the DP table updates, causing incorrect results.Final Answer:
Using dp = [[0] * (n+1)] * (m+1) causes all rows to reference the same list -> Option AQuick Check:
Shared row references cause bugs [OK]
- Using wrong dp initialization
- Incorrect loop ranges
- Wrong indexing in condition
s1 = 'intention' and s2 = 'execution', what is the minimum edit distance using Levenshtein algorithm? Consider insert, delete, and replace operations each cost 1.Solution
Step 1: Compare strings and count minimal edits
Transform 'intention' to 'execution' requires the minimum number of single-character edits (insertions, deletions, substitutions) of 6.Step 2: Confirm with Levenshtein DP logic
Dynamic programming confirms minimum edits as 6 for these strings.Final Answer:
6 -> Option DQuick Check:
Levenshtein('intention','execution') = 6 [OK]
- Underestimating number of replacements
- Ignoring insertions
- Confusing with Hamming distance
Ready to prove it?
Pass the test and this pattern moves unknown
