Problem overview
The method MinDistance receives two strings, word1 and word2, and returns the fewest operations that turn word1 into word2. Three operations exist and each costs one: insert a character anywhere, delete a character, or replace a character with another one. Either word may be empty, and both have at most 500 lowercase letters.
For "horse" and "ros" the answer is 3: replace h with r, delete the second r, delete e. For "intention" and "execution" it is 5. For "" and "abc" it is 3, three inserts, because each operation adds at most one character.
The lesson asks for O(m · n) time and space, where m and n are the two lengths.
Approach: solve every pair of prefixes
Trying every sequence of operations explodes quickly. The way out is to solve smaller versions of the same question. Let dp[i, j] be the fewest operations that turn the first i characters of word1 into the first j characters of word2. The table has (m + 1) × (n + 1) entries, and the answer is dp[m, n].
Row 0 and column 0 stand for an empty prefix, and they are known without any search. Turning i characters into nothing takes i deletes, so dp[i, 0] = i. Turning nothing into j characters takes j inserts, so dp[0, j] = j.
Every other entry comes from the last characters of the two prefixes, word1[i - 1] and word2[j - 1]:
- They are equal. They need no operation, so
dp[i, j] = dp[i - 1, j - 1]. Spending an operation on either of them is never better. - They differ. One operation fixes the end, and
dp[i, j]is 1 plus the smallest of three neighbours.dp[i - 1, j - 1]is a replace: both prefixes lose their last character.dp[i - 1, j]is a delete ofword1's last character.dp[i, j - 1]is an insert ofword2's last character at the end, which then matches, leaving a shorter prefix ofword2.
Filling the table row by row, left to right, guarantees that the three neighbours above, to the left and diagonally up-left are ready when an entry is computed.
The table for "horse" and "ros"
Here is the whole table for word1 = "horse" (rows 0 to 5) and word2 = "ros" (columns 0 to 3):
0 1 2 3
1 1 2 3
2 2 1 2
3 2 2 2
4 3 3 2
5 4 4 3
The bottom-right entry, dp[5, 3], is 3, the answer. Two entries show both cases. In dp[3, 2] ("hor" against "ro") the last characters r and o differ, and the smallest neighbour is dp[2, 2] = 1, so the entry is 1 + 1 = 2. In dp[4, 3] ("hors" against "ros") the last characters are both s, so the entry copies dp[3, 2], which is 2.
Step by step
- Store the lengths
word1.Lengthandword2.Lengthinmandn. They are used in the table size and in every loop bound. - Create
dpas anint[,]withm + 1rows andn + 1columns. The extra row and column hold the empty prefixes. A new array starts filled with zeros. - Loop
ifrom 0 tomto fill column 0, and setdp[i, 0]toi:ideletes empty the prefix. - Loop
jfrom 0 tonto fill row 0, and setdp[0, j]toj:jinserts build the prefix. The cornerdp[0, 0]is set twice, both times to 0. - Loop over the remaining rows,
ifrom 1 tom, and over the columns,jfrom 1 ton. Row 0 and column 0 are filled already. - Compare
word1[i - 1]withword2[j - 1]. The firsticharacters end at indexi - 1, because the strings count from 0 and the table counts characters. When equal, copydp[i - 1, j - 1]. - Otherwise take 1 plus the smallest of the three neighbours, using one
Math.Mininside another, since each call compares only two values. - Return
dp[m, n]. It covers all of both words.
The solution in C#
class Solution {
public int MinDistance(string word1, string word2) {
int m = word1.Length;
int n = word2.Length;
int[,] dp = new int[m + 1, n + 1];
for (int i = 0; i <= m; i++) {
dp[i, 0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0, j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i, j] = dp[i - 1, j - 1];
} else {
dp[i, j] = 1 + Math.Min(dp[i - 1, j - 1], Math.Min(dp[i - 1, j], dp[i, j - 1]));
}
}
}
return dp[m, n];
}
}
The C# details worth knowing:
- Rectangular arrays.
int[,]is a single block indexed with one pair of brackets,dp[i, j]. Code written asdp[i][j]belongs to jagged arrays (int[][]) and won't compile here. For a 3 by 4 table,GetLength(0)is 3,GetLength(1)is 4 andLengthis 12, the total number of cells. - Strings are indexable.
word1[i - 1]is achar, and==on twocharvalues compares them directly. A string can't be changed in place, but this solution only reads characters. - Length is a property. Write
word1.Lengthwithout parentheses. The empty string""has length 0, so the table is 1 byn + 1and the loops handle it with no special case. - Bounds are checked. Reading
word1[i]wheniequals the length throws anIndexOutOfRangeExceptioninstead of reading garbage. This is the usual result of forgetting the- 1. Math.Mintakes two arguments. To take the minimum of three values, nest the calls, as the lesson does.
Complexity
Time: O(m · n). The table has (m + 1) × (n + 1) entries and each one takes constant work, a comparison and at most three reads.
Space: O(m · n). The full table is kept, which makes the solution easy to follow. Each row reads only itself and the row above, so two rows of n + 1 numbers would be enough and would bring the space down to O(n). With words of 500 characters the full table has about 251,000 integers, which is small.
Edge cases and common mistakes
- Empty words.
("", "abc")returns 3,("abc", "")returns 3 and("", "")returns 0, all straight from the first row and column. - Equal words.
("abc", "abc")returns 0: every comparison copies the diagonal, which stays 0. - Swapped neighbours.
("ab", "ba")returns 2. There is no swap operation, so two replaces are needed. - One replace.
("a", "b")returns 1. - Off by one on the characters. The table index
icorresponds to characteri - 1of the word. Usingword1[i]is wrong and throws at the last row. - Forgetting the base row and column. With them left at zero,
("", "abc")would give 0 instead of 3. - Missing a neighbour in the minimum. Using only the diagonal ignores inserts and deletes, and words of different lengths come out wrong.
- Forgetting the 1. The operation itself has to be counted, so the minimum alone is not the answer for differing characters.
Testing
"horse"and"ros"gives3."intention"and"execution"gives5.""and"abc"gives3."abc"and""gives3."ab"and"ba"gives2."a"and"b"gives1.- Two words of 500 different letters (
'a'repeated against'b'repeated) give500.
