Open In App

Edit Distance | DP using Memoization

Improve
Improve
Like Article
Like
Save
Share
Report

Given two strings str1 and str2 and below operations that can be performed on str1. Find the minimum number of edits (operations) required to convert ‘str1’ into ‘str2’. 

  • Insert
  • Remove
  • Replace

All of the above operations are of equal cost. 

Examples: 

Input: str1 = “geek”, str2 = “gesek” 
Output:
We can convert str1 into str2 by inserting a ‘s’.

Input: str1 = “cat”, str2 = “cut” 
Output:
We can convert str1 into str2 by replacing ‘a’ with ‘u’.

Input: str1 = “sunday”, str2 = “saturday” 
Output:
Last three and first characters are same. We basically 
need to convert “un” to “atur”. This can be done using 
below three operations. 
Replace ‘n’ with ‘r’, insert t, insert a

What are the subproblems in this case? The idea is to process all characters one by one starting from either from left or right sides of both strings. Let us traverse from right corner, there are two possibilities for every pair of characters being traversed. The following are the conditions: 

  1. If last characters of two strings are same, nothing much to do. Ignore last characters and get count for remaining strings. So we recur for lengths m-1 and n-1.
  2. Else (If last characters are not same), we consider all operations on ‘str1’, consider all three operations on last character of first string, recursively compute minimum cost for all three operations, and take minimum of three values. 
    • Insert: Recur for m and n-1
    • Remove: Recur for m-1 and n
    • Replace: Recur for m-1 and n-1

Below is the implementation of the above approach: 

C++




// A Naive recursive C++ program to find minimum number
// operations to convert str1 to str2
#include <bits/stdc++.h>
using namespace std;
 
// Utility function to find minimum of three numbers
int min(int x, int y, int z)
{
    return min(min(x, y), z);
}
 
int editDist(string str1, string str2, int m, int n)
{
    // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0)
        return n;
 
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0)
        return m;
 
    // If last characters of two strings are same, nothing
    // much to do. Ignore last characters and get count for
    // remaining strings.
    if (str1[m - 1] == str2[n - 1])
        return editDist(str1, str2, m - 1, n - 1);
 
    // If last characters are not same, consider all three
    // operations on last character of first string, recursively
    // compute minimum cost for all three operations and take
    // minimum of three values.
    return 1 + min(editDist(str1, str2, m, n - 1), // Insert
                   editDist(str1, str2, m - 1, n), // Remove
                   editDist(str1, str2, m - 1, n - 1) // Replace
                   );
}
 
// Driver program
int main()
{
    // your code goes here
    string str1 = "sunday";
    string str2 = "saturday";
 
    cout << editDist(str1, str2, str1.length(), str2.length());
 
    return 0;
}


Java




// A Naive recursive Java program to find minimum number
// operations to convert str1 to str2
class EDIST {
    static int min(int x, int y, int z)
    {
        if (x <= y && x <= z)
            return x;
        if (y <= x && y <= z)
            return y;
        else
            return z;
    }
 
    static int editDist(String str1, String str2, int m, int n)
    {
        // If first string is empty, the only option is to
        // insert all characters of second string into first
        if (m == 0)
            return n;
 
        // If second string is empty, the only option is to
        // remove all characters of first string
        if (n == 0)
            return m;
 
        // If last characters of two strings are same, nothing
        // much to do. Ignore last characters and get count for
        // remaining strings.
        if (str1.charAt(m - 1) == str2.charAt(n - 1))
            return editDist(str1, str2, m - 1, n - 1);
 
        // If last characters are not same, consider all three
        // operations on last character of first string, recursively
        // compute minimum cost for all three operations and take
        // minimum of three values.
        return 1 + min(editDist(str1, str2, m, n - 1), // Insert
                       editDist(str1, str2, m - 1, n), // Remove
                       editDist(str1, str2, m - 1, n - 1) // Replace
                       );
    }
 
    public static void main(String args[])
    {
        String str1 = "sunday";
        String str2 = "saturday";
 
        System.out.println(editDist(str1, str2, str1.length(), str2.length()));
    }
}


Python




# A Naive recursive Python program to find minimum number
# operations to convert str1 to str2
def editDistance(str1, str2, m, n):
 
    # If first string is empty, the only option is to
    # insert all characters of second string into first
    if m == 0:
         return n
 
    # If second string is empty, the only option is to
    # remove all characters of first string
    if n == 0:
        return m
 
    # If last characters of two strings are same, nothing
    # much to do. Ignore last characters and get count for
    # remaining strings.
    if str1[m-1]== str2[n-1]:
        return editDistance(str1, str2, m-1, n-1)
 
    # If last characters are not same, consider all three
    # operations on last character of first string, recursively
    # compute minimum cost for all three operations and take
    # minimum of three values.
    return 1 + min(editDistance(str1, str2, m, n-1),    # Insert
                   editDistance(str1, str2, m-1, n),    # Remove
                   editDistance(str1, str2, m-1, n-1)    # Replace
                   )
 
# Driver program to test the above function
str1 = "sunday"
str2 = "saturday"
print editDistance(str1, str2, len(str1), len(str2))


C#




// A Naive recursive C# program to
// find minimum numberoperations
// to convert str1 to str2
using System;
 
class GFG {
    static int min(int x, int y, int z)
    {
        if (x <= y && x <= z)
            return x;
        if (y <= x && y <= z)
            return y;
        else
            return z;
    }
 
    static int editDist(String str1, String str2, int m, int n)
    {
        // If first string is empty, the only option is to
        // insert all characters of second string into first
        if (m == 0)
            return n;
 
        // If second string is empty, the only option is to
        // remove all characters of first string
        if (n == 0)
            return m;
 
        // If last characters of two strings are same, nothing
        // much to do. Ignore last characters and get count for
        // remaining strings.
        if (str1[m - 1] == str2[n - 1])
            return editDist(str1, str2, m - 1, n - 1);
 
        // If last characters are not same, consider all three
        // operations on last character of first string, recursively
        // compute minimum cost for all three operations and take
        // minimum of three values.
        return 1 + min(editDist(str1, str2, m, n - 1), // Insert
                       editDist(str1, str2, m - 1, n), // Remove
                       editDist(str1, str2, m - 1, n - 1) // Replace
                       );
    }
 
    // Driver code
    public static void Main()
    {
        String str1 = "sunday";
        String str2 = "saturday";
        Console.WriteLine(editDist(str1, str2, str1.Length,
                                   str2.Length));
    }
}


Javascript




// A Naive recursive Javascript
// program to find minimum number
// operations to convert str1 to str2
 
// Utility function to find minimum of three numbers
function min(x, y, z)
{
    return Math.min(Math.min(x, y), z);
}
 
function editDist(str1, str2, m, n)
{
    // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0)
        return n;
 
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0)
        return m;
 
    // If last characters of two strings are same, nothing
    // much to do. Ignore last characters and get count for
    // remaining strings.
    if (str1[m - 1] == str2[n - 1])
        return editDist(str1, str2, m - 1, n - 1);
 
    // If last characters are not same, consider all three
    // operations on last character of first string, recursively
    // compute minimum cost for all three operations and take
    // minimum of three values.
    return 1 + min(editDist(str1, str2, m, n - 1), // Insert
                   editDist(str1, str2, m - 1, n), // Remove
                   editDist(str1, str2, m - 1, n - 1) // Replace
                   );
}
 
// Driver program
// your code goes here
var str1 = "sunday";
var str2 = "saturday";
console.log( editDist(str1, str2, str1.length, str2.length));


Output

3

Space complexity :- O(MN)

The time complexity of above solution is O(3^n) which is exponential. The worst-case happens when none of characters of two strings match. Below is a recursive call diagram for worst case. 

EditDistance

We can see that many subproblems are solved, again and again, for example, eD(2, 2) is called three times. Since same subproblems are called again, this problem has Overlapping Subproblems property. So Edit Distance problem has both properties (see this and this) of a dynamic programming problem. Like other typical Dynamic Programming(DP) problems, recomputations of same subproblems can be avoided by constructing a temporary array that stores results of subproblems. The bottom-up approach can be found here.

This code uses top-down dynamic programming and memoization to solve the problem. The repetitive calls in the recursive code can be avoided by using a 2D array to store the results of previous calculations, reducing the time complexity. The 2D array is necessary because two parameters in the recursive calls change value and there are cases of repetitive calls. 

Below are the steps: 

  • Initialize a 2-D DP array of size m *n with -1 at all the index.
  • On every recursive call, store the return value at dp[m][n] so that if func(m, n) is called again, it can be answered in O(1) without using recursion.
  • Check if the recursive call has been visited previously or not by checking the value at dp[m][n].

Below is the implementation of the above approach:  

C++




// A memoization program to find minimum number
// operations to convert str1 to str2
#include <bits/stdc++.h>
using namespace std;
 
// Maximum 2-D array column size
const int maximum = 1000;
 
// Utility function to find minimum of three numbers
int min(int x, int y, int z)
{
    return min(min(x, y), z);
}
 
int editDist(string str1, string str2, int m, int n, int dp[][maximum])
{
    // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0)
        return n;
 
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0)
        return m;
 
    // if the recursive call has been
    // called previously, then return
    // the stored value that was calculated
    // previously
    if (dp[m - 1][n - 1] != -1)
        return dp[m - 1][n - 1];
 
    // If last characters of two strings are same, nothing
    // much to do. Ignore last characters and get count for
    // remaining strings.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    if (str1[m - 1] == str2[n - 1])
        return dp[m - 1][n - 1] = editDist(str1, str2, m - 1, n - 1, dp);
 
    // If last characters are not same, consider all three
    // operations on last character of first string, recursively
    // compute minimum cost for all three operations and take
    // minimum of three values.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    return dp[m - 1][n - 1] = 1 + min(editDist(str1, str2, m, n - 1, dp), // Insert
                                      editDist(str1, str2, m - 1, n, dp), // Remove
                                      editDist(str1, str2, m - 1, n - 1, dp) // Replace
                                      );
}
 
// Driver Code
int main()
{
 
    string str1 = "sunday";
    string str2 = "saturday";
    int m = str1.length();
    int n = str2.length();
 
    // Declare a dp array which stores
    // the answer to recursive calls
    int dp[m][maximum];
 
    // initially all index with -1
    memset(dp, -1, sizeof dp);
 
    // Function call
    // memoization and top-down approach
    cout << editDist(str1, str2, m, n, dp);
 
    return 0;
}


Java




// A memoization program to find minimum number
// operations to convert str1 to str2
import java.util.*;
 
class GFG
{
   
  // Maximum 2-D array column size
  static int maximum = 1000;
 
  // Utility function to find minimum of three numbers
  static int min(int x, int y, int z)
  {
    return Math.min((Math.min(x, y)), z);
  }
 
  static int editDist(String str1, String str2, int m,
                      int n, int[][] dp)
  {
     
    // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0)
      return n;
 
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0)
      return m;
 
    // if the recursive call has been
    // called previously, then return
    // the stored value that was calculated
    // previously
    if (dp[m - 1][n - 1] != -1)
      return dp[m - 1][n - 1];
 
    // If last characters of two strings are same,
    // nothing much to do. Ignore last characters and
    // get count for remaining strings.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    if (str1.charAt(m - 1) == str2.charAt(n - 1))
      return dp[m - 1][n - 1]
      = editDist(str1, str2, m - 1, n - 1, dp);
 
    // If last characters are not same, consider all
    // three operations on last character of first
    // string, recursively compute minimum cost for all
    // three operations and take minimum of three
    // values.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    return dp[m - 1][n - 1]
      = 1
      + min(editDist(str1, str2, m, n - 1,
                     dp), // Insert
            editDist(str1, str2, m - 1, n,
                     dp), // Remove
            editDist(str1, str2, m - 1, n - 1,
                     dp) // Replace
           );
  }
 
  // Driver code
  public static void main(String[] args)
  {
    String str1 = "sunday";
    String str2 = "saturday";
    int m = str1.length();
    int n = str2.length();
 
    // Declare a dp array which stores
    // the answer to recursive calls
    int[][] dp = new int[m][maximum];
    for (int i = 0; i < dp.length; i++)
      Arrays.fill(dp[i], -1);
 
    // Function call
    // memoization and top-down approach
    System.out.println(editDist(str1, str2, m, n, dp));
  }
}
 
// This code is contributed by Karandeep Singh


Python3




# A memoization program to find minimum number
# operations to convert str1 to str2
def editDistance(str1, str2, m, n, d = {}):
     
    key = m, n
 
    # If first string is empty, the only option
    # is to insert all characters of second
    # string into first
    if m == 0:
        return n
 
    # If second string is empty, the only
    # option is to remove all characters
    # of first string
    if n == 0:
        return m
 
    if key in d:
        return d[key]
         
    # If last characters of two strings are same,
    # nothing much to do. Ignore last characters
    # and get count for remaining strings.
    if str1[m - 1] == str2[n - 1]:
        return editDistance(str1, str2, m - 1, n - 1)
 
    # If last characters are not same, consider
    # all three operations on last character of
    # first string, recursively compute minimum
    # cost for all three operations and take
    # minimum of three values.
     
    # Store the returned value at dp[m-1][n-1]
    # considering 1-based indexing
    d[key] = 1 + min(editDistance(str1, str2, m, n - 1), # Insert
                     editDistance(str1, str2, m - 1, n), # Remove
                     editDistance(str1, str2, m - 1, n - 1)) # Replace
    return d[key]
 
# Driver code
str1 = "sunday"
str2 = "saturday"
 
print(editDistance(str1, str2, len(str1), len(str2)))
 
# This code is contributed by puranjanprithu


C#




// C# code for the above approach
using System;
 
class GFG
{
  // Maximum 2-D array column size
  static int maximum = 1000;
 
  // Utility function to find minimum of three numbers
  static int min(int x, int y, int z)
  {
    return Math.Min(Math.Min(x, y), z);
  }
 
  static int editDist(string str1, string str2, int m,
                      int n, int[,] dp)
  {
 
    // If first string is empty, the only option is to
    // insert all characters of second string into first
    if (m == 0)
      return n;
 
    // If second string is empty, the only option is to
    // remove all characters of first string
    if (n == 0)
      return m;
 
    // if the recursive call has been
    // called previously, then return
    // the stored value that was calculated
    // previously
    if (dp[m - 1, n - 1] != -1)
      return dp[m - 1, n - 1];
 
    // If last characters of two strings are same,
    // nothing much to do. Ignore last characters and
    // get count for remaining strings.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    if (str1[m - 1] == str2[n - 1])
      return dp[m - 1, n - 1] = editDist(str1, str2, m - 1, n - 1, dp);
 
    // If last characters are not same, consider all
    // three operations on last character of first
    // string, recursively compute minimum cost for all
    // three operations and take minimum of three
    // values.
 
    // Store the returned value at dp[m-1][n-1]
    // considering 1-based indexing
    return dp[m - 1, n - 1] = 1 + min(editDist(str1, str2, m, n - 1,
                                               dp), // Insert
                                      editDist(str1, str2, m - 1, n,
                                               dp), // Remove
                                      editDist(str1, str2, m - 1, n - 1,
                                               dp) // Replace
                                     );
  }
 
  // Driver code
  static void Main(string[] args)
  {
    string str1 = "sunday";
    string str2 = "saturday";
    int m = str1.Length;
    int n = str2.Length;
 
    // Declare a dp array which stores
    // the answer to recursive calls
    int[,] dp = new int[m, maximum];
    for (int i = 0; i < dp.GetLength(0); i++)
      for (int j = 0; j < dp.GetLength(1); j++)
        dp[i, j] = -1;
 
    // Function call
    // memoization and top-down approach
    Console.WriteLine(editDist(str1, str2, m, n, dp));
  }
}
 
// This code is contributed by lokeshpotta20.


Javascript




// A memoization function to find the minimum number of operations to convert str1 to str2
function editDist(str1, str2, m, n, dp) {
  // If the first string is empty, the only option is to
  // insert all characters of the second string into the first
  if (m === 0) return n;
 
  // If the second string is empty, the only option is to
  // remove all characters of the first string
  if (n === 0) return m;
 
  // if the recursive call has been
  // called previously, then return
  // the stored value that was calculated
  // previously
  if (dp[m - 1][n - 1] !== undefined) return dp[m - 1][n - 1];
 
  // If the last characters of two strings are the same, there's not much to do.
  // Ignore last characters and get the count for the remaining strings.
 
  // Store the returned value at dp[m-1][n-1]
  // considering 1-based indexing
  if (str1[m - 1] === str2[n - 1])
    return (dp[m - 1][n - 1] = editDist(str1, str2, m - 1, n - 1, dp));
 
  // If the last characters are not the same, consider all three
  // operations on the last character of the first string, recursively
  // compute the minimum cost for all three operations and take
  // the minimum of three values.
 
  // Store the returned value at dp[m-1][n-1]
  // considering 1-based indexing
  return (dp[m - 1][n - 1] =
    1 +
    Math.min(
      editDist(str1, str2, m, n - 1, dp), // Insert
      editDist(str1, str2, m - 1, n, dp), // Remove
      editDist(str1, str2, m - 1, n - 1, dp) // Replace
    ));
}
 
// Driver code
function main() {
  let str1 = "sunday";
  let str2 = "saturday";
  let m = str1.length;
  let n = str2.length;
 
  // Declare a dp array which stores
  // the answer to recursive calls
  let dp = Array(m)
    .fill(0)
    .map(() => Array(n).fill(undefined));
 
  // Function call
  // memoization and top-down approach
  console.log(editDist(str1, str2, m, n, dp));
}
 
main();


Output

3

Complexity Analysis:

  • Time Complexity: O(M * N) 
  • Auxiliary Space: O(M * N)


Last Updated : 17 Apr, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads