Edit Distance Problem Levenshtein in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find the edit distance between two words grows as the words get longer.
The question is: How does the number of steps change when the input words become bigger?
Analyze the time complexity of the following code snippet.
fn levenshtein_distance(s1: &str, s2: &str) -> usize {
let len1 = s1.len();
let len2 = s2.len();
let mut dp = vec![vec![0; len2 + 1]; len1 + 1];
for i in 0..=len1 {
dp[i][0] = i;
}
for j in 0..=len2 {
dp[0][j] = j;
}
for i in 1..=len1 {
for j in 1..=len2 {
dp[i][j] = if s1.as_bytes()[i - 1] == s2.as_bytes()[j - 1] {
dp[i - 1][j - 1]
} else {
1 + std::cmp::min(dp[i - 1][j - 1], std::cmp::min(dp[i][j - 1], dp[i - 1][j]))
};
}
}
dp[len1][len2]
}
This code calculates the minimum number of edits needed to change one word into another using a table to store results.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops filling a 2D table of size (length of first word + 1) by (length of second word + 1).
- How many times: The inner loop runs for each character of the second word, and the outer loop runs for each character of the first word, so total steps are roughly the product of the two lengths.
As the words get longer, the number of operations grows by multiplying their lengths.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 100 steps |
| 100 | About 10,000 steps |
| 1000 | About 1,000,000 steps |
Pattern observation: Doubling the length roughly quadruples the work, showing a square growth pattern.
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 only looks at one word at a time, so it should be O(n)."
[OK] Correct: The algorithm compares every character of the first word with every character of the second, so it must check all pairs, making it grow with both lengths multiplied.
Understanding this time complexity helps you explain how dynamic programming solves problems efficiently by avoiding repeated work, a key skill in many coding challenges.
"What if we used recursion without storing results? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of Levenshtein distance
It counts the smallest number of edits (insertions, deletions, substitutions) needed to convert one string into another.Step 2: Compare options with the definition
Only The minimum number of single-character edits to change one string into the other correctly describes this minimum edit count concept.Final Answer:
The minimum number of single-character edits to change one string into the other -> Option AQuick Check:
Levenshtein distance = minimum edits [OK]
- Confusing with longest common subsequence
- Thinking it measures string length difference only
- Assuming it counts matching characters
Solution
Step 1: Identify correct dimensions for DP table
The DP table for Levenshtein distance has (m+1) rows and (n+1) columns, where m and n are string lengths.Step 2: Match Rust vector initialization syntax
let mut dp = vec![vec![0; n+1]; m+1]; correctly creates a mutable 2D vector with m+1 rows and n+1 columns filled with zeros.Final Answer:
let mut dp = vec![vec![0; n+1]; m+1]; -> Option BQuick Check:
Rows = m+1, Columns = n+1, mutable vector [OK]
- Swapping m and n in dimensions
- Forgetting to add 1 to lengths
- Not making vector mutable
fn main() {
let s1 = "kitten";
let s2 = "sitting";
let m = s1.len();
let n = s2.len();
let mut dp = vec![vec![0; n + 1]; m + 1];
for i in 0..=m {
dp[i][0] = i;
}
for j in 0..=n {
dp[0][j] = j;
}
for i in 1..=m {
for j in 1..=n {
if s1.as_bytes()[i - 1] == s2.as_bytes()[j - 1] {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + std::cmp::min(
dp[i - 1][j - 1],
std::cmp::min(dp[i][j - 1], dp[i - 1][j]),
);
}
}
}
println!("{}", dp[m][n]);
}Solution
Step 1: Understand the strings and DP setup
Strings are "kitten" (length 6) and "sitting" (length 7). The DP table is correctly initialized and filled using Levenshtein logic.Step 2: Calculate Levenshtein distance manually
Transform "kitten" to "sitting" requires 3 substitutions and 1 insertion, total 4 edits.Final Answer:
4 -> Option AQuick Check:
Levenshtein("kitten", "sitting") = 4 [OK]
- Counting only substitutions, ignoring insertions
- Misindexing string characters
- Off-by-one errors in loops
for i in 1..m {
for j in 1..n {
if s1.as_bytes()[i - 1] == s2.as_bytes()[j - 1] {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + std::cmp::min(
dp[i - 1][j - 1],
std::cmp::min(dp[i][j - 1], dp[i - 1][j]),
);
}
}
}Solution
Step 1: Check loop ranges for DP filling
Loops use 1..m and 1..n which exclude the last index (m and n). They should be inclusive (1..=m and 1..=n) to cover all characters.Step 2: Verify other code parts
Indexing with i-1 and j-1 is correct, min is used properly, and initialization is assumed done outside snippet.Final Answer:
Loop ranges should be 1..=m and 1..=n to include last indices -> Option CQuick Check:
Inclusive loops needed for full DP table [OK]
- Using exclusive ranges missing last character
- Incorrect indexing causing panic
- Skipping DP initialization
s1 = "intention" and s2 = "execution", what is the Levenshtein distance computed by the Rust algorithm?
Use the standard operations: insert, delete, substitute with cost 1 each.Solution
Step 1: Analyze the strings and possible edits
"intention" (i n t e n t i o n) to "execution" (e x e c u t i o n). Minimal edits: substitute i->e, n->x, t->e, e->c, n->u, plus one insertion or deletion needed, total 6 edits.Step 2: Confirm with Levenshtein algorithm logic
The DP computes the minimum cost as 6.Final Answer:
6 -> Option DQuick Check:
Levenshtein("intention", "execution") = 6 [OK]
- Ignoring insertions or deletions
- Miscounting substitutions
- Assuming equal length means zero edits
Ready to prove it?
Pass the test and this pattern moves unknown
