Edit distance — dynamic programming

This article covers the edit distance pattern of dynamic programming. Most of this article is a summary of the explanation covered by Steven Skiena in the Algorithm Design Manual. I’ve clubbed together the leetcode problems as well as the text problems on Steven Skiena’s book.

What is the edit distance problem?

Say, I’ve been provided with two strings — “abccbcdcd” (Source string) and “accdcbbda” (Destination string). I’m allowed to use three operations — INSERT, DELETE and SUBSTITUTION — each with a cost of 1. What would be the minimum possible cost to convert the source string to the destination string?

If we need to build a recurrence relation for it, let’s try to think about a string of length = 1 first, and then try to generalize the solution to strings with length > 1.

Example : source string : “a” , destination string : “b” — we could modify from “a” to “b” by the following steps:

  • substitute “a” to “b” — cost: 1
  • Delete “a” , Insert “b” — cost: 2
  • Insert “b”, Delete “a” — cost: 2

The most effective way to arrive at the solution is through substitution which has a cost of only 1.

If we generalize the above, we will come up with a cost matrix.

cost[i][j] = Minimum ( cost[i-1][j-1] + 1 (substitution) , cost[i-1][j] + 1 (insertion), cost[i][j-1] + 1 (deletion).

Here ‘i’ represents the index of the source string and ‘j’ represents the index of the destination string. So, if we feed in the recurrence above to our example, we see :

cost[1][1] = minimum ( cost[0][0] + 1 (substitution) , cost[0][1] + 1 (insertion), cost[1][0] + 1 (deletion))

cost[0][0] = comparing(“”, “”) = 0 (as the source and destination strings are the same)

cost[0][1] = comparing(“”, “b”) = 1 (as the destination string is one character more than the source string and we need to insert on the source string to get the destination string)

cost[1][0] = comparing(“a”, “”) = 1 (as the destination string is one character short of the source string and we need to delete the source string character to get the destination string character)

Thus, cost[1][1] = minimum(0 + 1, 1 + 1, 1 + 1) = 1

What happens if the characters in both strings are the same?

In this scenario, we would not need to add any cost to edit the string at all.

cost[i][j] = if (source[i] == destination[j]) => cost[i-1][j-1]

Thus, clubbing together both conditions, we get:

cost[i][j] = cost[i-1][j-1] , if (source[i] == destination[j])

cost[i][j] = Minimum ( cost[i-1][j-1] + 1 (substitution) , cost[i-1][j] + 1 (insertion), cost[i][j-1] + 1 (deletion), otherwise

Now, let’s formulate the source code for it:

Let’s define an enum for the actions : INSERT, MATCH, SUBSTITUTE, DELETE. We also add an added variable NOTHING — which signifies no action and is used to initialize the DP table.

Now, we formulate our recurrence relation to Code :

Cell represents the DP matrix that we intend to build up on.

Now, we compare the strings and evaluate the editDistance cost in this function:

If we wish to retrieve the sequence of actions that we need to take in order to move from the source to the destination string — all we would need to do is write up a recursive function that traces the sequence of steps from the last cell.

The call to getActions would be -> getActions("", source.length(), destination.length());

This was a basic explanation and implementation of Edit Distance. In the next few articles, we will discuss about the variants of edit distance.

I’m a software engineer with a strong interest in algorithms. I love solving puzzles and love the aha moment of arriving at a solution after hours of struggle.