Edit distance

LeetCode problem 72. Edit distance.

## Problem description

Given two strings `word1` and `word2`, return *the minimum number of operations* required to convert `word1` to `word2`.

You have the following three operations permitted on a word:

* Insert a character
* Delete a character
* Replace a character

Example 1
**Input:** word1 = "horse", word2 = "ros"
**Output:** 3
**Explanation:**
1. horse → rorse (replace 'h' with 'r')
2. rorse → rose (remove 'r')
3. rose → ros (remove 'e')

Example 2
**Input:** word1 = "intention", word2 = "execution"
**Output:** 5
**Explanation:**
1. intention → inention (remove 't')
2. inention → enention (replace 'i' with 'e')
3. enention → exention (replace 'n' with 'x')
4. exention → exection (replace 'n' with 'c')
5. exection → execution (insert 'u')

Constraints
* `0 <= word1.length, word2.length <= 500`
* `word1` and `word2` consist of lowercase English letters.

## Analysis

### Defining states

Here are some basic facts we notice:

1. If one input string is empty, then the edit distance between the two strings is the length of the non-empty one.
2. If two input strings end with the same character, then removing the ending character from both strings yields the same edit distance.
3. If two input strings (`str1` and `str2`) end with different characters, then from the bottom-up perspective, this can be interpreted as:
	1. Edit one of the string into the other string *minus* the last character.
	2. Append the last character excluded above to get the other string.

So we can consider `dist[i][j] =` edit distance between `str1[0..i-1]` and `str2[0..j-1]`. This represents each single state in the edit distance table.

### Defining transitions

* If either `str1` or `str2` is empty, then the edit distance is the length of the other non-empty string.
* In the general case, consider substrings `str1[0..i]` and `str2[0..j]` (i, j > 0):
	+ If `str1[i-1] == str2[j-1]` (i.e. the last characters of the two substrings are the same), then obviously `dist[i][j] = dist[i-1][j-1]`.
	+ Otherwise, `dist[i][j]` may be given by referring to these three smaller subproblems, and picking the *minimum* of these three results:
		1. **`dist[i-1][j-1] + 1`**: This means that after editing substrings `str1[0..i-2]` to `str2[0..j-2]`, we replace `str1[i-1]` into `str2[j-1]` as the last edit.
		2. **`dist[i][j-1] + 1`**: This means that after editing substrings `str1[0..i-1]` to `str2[0..j-2]`, we append `str2[j-1]` as the last edit.
		3. **`dist[i-1][j] + 1`**: This means that after editing substrings `str1[0..i-2]` to `str2[0..j-1]`, we append `str1[i-1]` as the last edit.
* So the transition formula is: `dist[i][j] =`
	+ If `str1[i-1] == str2[j-1]`: `dist[i-1][j-1]`
	+ Otherwise, minimum of:
		1. `dist[i-1][j-1] + 1`
		2. `dist[i][j-1] + 1`
		3. `dist[i-1][j] + 1`

### Fill in the table

Example: `str1 = "grass"`, `str2 = "fast"`
The steps are as follows:
1. Create a `dist[][]` of size (5+1) rows × (4+1) columns.
2. Fill in the top row with values 0-5, and the left column with values 0-4.
3. Fill in other cells from top left to bottom right. The rules are as follows:
For `dist[i][j]`:
	* If `str1[i-1] == str2[j-1]`, then `dist[i][j] == dist[i-1][j-1]`.
	* Otherwise, pick the min of:
		1. `dist[i-1][j] + 1`
		2. `dist[i][j-1] + 1`
		3. `dist[i-1][j-1] + 1`
4. Return `dist[5][4]`.

The filled table is as follows:

So `editDist("grass", "fast") = 3`.
1. Remove "g".
2. Replace "r" to "f".
3. Replace the last "s" to "t".

### Complexity analysis

Time complexity
Let *m*, *n* be the lengths of `str1` and `str2` respectively.
Then the `dist[][]` table has size (m+1) × (n+1).
Filling in entry table takes constant time.
So the total time complexity = **O(m⋅n)**.
Space complexity
Since we only used the `dist[][]` table on the heap, the total space complexity = **O(m⋅n)**.
## Solution

### C++ solution

```
class Solution {
public:
 int minDistance(const std::string& word1, const std::string& word2) {
 const int rows = word1.length() + 1, cols = word2.length() + 1;

 // corner case: at least one of the words are empty
 if (rows == 0) {
 return cols;
 }
 else if (cols == 0) {
 return rows;
 }

 // fill up dist[][] table
 std::vector<std::vector<int>> dist(rows);
 for (int row = 0; row < rows; ++row) {
 dist[row] = std::vector<int>(cols);

 for (int col = 0; col < cols; ++col) {
 int& cell = dist[row][col];

 // top row / left column
 if (row == 0) {
 cell = col;
 }
 else if (col == 0) {
 cell = row;
 }
 // transtion cells
 else {
 // case 1: last letters of two substrings are equal
 if (word1[row-1] == word2[col-1]) {
 cell = dist[row-1][col-1];
 }
 // case 2: last letters of two substrings are different
 else {
 cell = INT\_MAX;
 // 1. check up left (last edit is replace)
 cell = std::min(cell, dist[row-1][col-1] + 1);
 // 2. check up (last edit is append word1[row-1])
 cell = std::min(cell, dist[row-1][col] + 1);
 // 3. check left (last edit is append word2[col-1])
 cell = std::min(cell, dist[row][col-1] + 1);
 }
 }
 }
 }

 return dist[rows-1][cols-1];
 }
};

```

### Java solution

```
class Solution {
 public int minDistance(String word1, String word2) {
 final int rows = word1.length() + 1, cols = word2.length() + 1;

 // corner case: at least one of the words are empty
 if (rows == 0) {
 return cols;
 }
 else if (cols == 0) {
 return rows;
 }

 // fill up dist[][] table
 final int[][] dist = new int[rows+1][cols+1];
 for (int row = 0; row < rows; ++row) {
 for (int col = 0; col < cols; ++col) {
 // top row / left column
 if (row == 0) {
 dist[row][col] = col;
 }
 else if (col == 0) {
 dist[row][col] = row;
 }
 // transtion cells
 else {
 // case 1: last letters of two substrings are equal
 if (word1.charAt(row-1) == word2.charAt(col-1)) {
 dist[row][col] = dist[row-1][col-1];
 }
 // case 2: last letters of two substrings are different
 else {
 dist[row][col] = Integer.MAX\_VALUE;
 // 1. check up left (last edit is replace)
 dist[row][col] = Math.min(dist[row][col], dist[row-1][col-1] + 1);
 // 2. check up (last edit is append word1[row-1])
 dist[row][col] = Math.min(dist[row][col], dist[row-1][col] + 1);
 // 3. check left (last edit is append word2[col-1])
 dist[row][col] = Math.min(dist[row][col], dist[row][col-1] + 1);
 }
 }
 }
 }

 return dist[rows-1][cols-1];
 }
}

```