Longest Common Subsequence | DP using Memoization - GeeksforGeeks

14 May, 2021
Given two strings s1 and s2, the task is to find the length of the longest common subsequence present in both of them.
Examples:  
Input: s1 = “ABCDGH”, s2 = “AEDFHR” Output: 3 LCS for input Sequences “AGGTAB” and “GXTXAYB” is “GTAB” of length 4.Input: s1 = “striver”, s2 = “raj” Output: 1  
The naive solution for this problem is to generate all subsequences of both given sequences and find the longest matching subsequence. This solution is exponential in terms of time complexity. The general recursive solution of the problem is to generate all subsequences of both given sequences and find the longest matching subsequence. The total possible combinations will be 2n. Hence, the recursive solution will take O(2n). Optimal Substructure: 
Let the input sequences are X[0... m-1] and Y[0...n-1] of lengths m and n respectively. And let L(X[0... m-1], Y[0...n-1]) be the length of LCS of the two sequences X and Y. Following is the recursive definition of L(X[0... m-1], Y[0...n-1]).
If last characters of both sequences match (or X[m-1] == Y[n-1]) then L(X[0... m-1], Y[0...n-1]) = 1 + L(X[0... m-2], Y[0...n-2])
If last characters of both sequences do not match (or X[m-1] != Y[n-1]) then L(X[0... m-1], Y[0...n-1]) = MAX (L(X[0... m-2], Y[0...n-1]), L(X[0... m-1], Y[0...n-2])
Given below is the recursive solution to the LCS problem: 
C++
Java
Python3
C#
PHP
Javascript
// A Naive C++ recursive implementation// of LCS of two strings#include <bits/stdc++.h>using namespace std; // Returns length of LCS for X[0..m-1], Y[0..n-1]int lcs(string X, string Y, int m, int n){    if (m == 0 || n == 0)        return 0;     if (X[m - 1] == Y[n - 1])        return 1 + lcs(X, Y, m - 1, n - 1);    else        return max(lcs(X, Y, m, n - 1),                   lcs(X, Y, m - 1, n));} // Driver Codeint main(){    string X = "AGGTAB";    string Y = "GXTXAYB";     // Find the length of string    int m = X.length();    int n = Y.length();     cout << "Length of LCS: " << lcs(X, Y, m, n);     return 0;}
// A Naive Java recursive implementation// of LCS of two strings class GFG { // Returns length of LCS for X[0..m-1], Y[0..n-1]    static int lcs(String X, String Y, int m, int n) {        if (m == 0 || n == 0) {            return 0;        }         if (X.charAt(m - 1) == Y.charAt(n - 1)) {            return 1 + lcs(X, Y, m - 1, n - 1);        } else {            return Math.max(lcs(X, Y, m, n - 1),                    lcs(X, Y, m - 1, n));        }    }// Driver Code     public static void main(String[] args) {        String X = "AGGTAB";        String Y = "GXTXAYB";         // Find the length of String        int m = X.length();        int n = Y.length();        System.out.println("Length of LCS: " + lcs(X, Y, m, n));     }} // This code is contributed by 29AjayKumar
# A Naive Python recursive implementation# of LCS of two strings # Returns length of LCS for X[0..m-1], Y[0..n-1]def lcs(X, Y, m, n):    if (m == 0 or n == 0):        return 0     if (X[m - 1] == Y[n - 1]):        return 1 + lcs(X, Y, m - 1, n - 1)    else:        return max(lcs(X, Y, m, n - 1),                   lcs(X, Y, m - 1, n)) # Driver Codeif __name__ == '__main__':    X = "AGGTAB"    Y = "GXTXAYB"     # Find the length of string    m = len(X)    n = len(Y)     print("Length of LCS:",           lcs(X, Y, m, n)) # This code is contributed by 29AjayKumar
// A Naive recursive C#implementation of// LCS of two stringsusing System; class GFG{ // Returns length of LCS for// X[0..m-1], Y[0..n-1]static int lcs(String X, String Y,               int m, int n){    if (m == 0 || n == 0)    {        return 0;    }     if (X[m - 1] == Y[n - 1])    {        return 1 + lcs(X, Y, m - 1, n - 1);    } else {        return Math.Max(lcs(X, Y, m, n - 1),                        lcs(X, Y, m - 1, n));    }} // Driver Codepublic static void Main(){    String X = "AGGTAB";    String Y = "GXTXAYB";     // Find the length of String    int m = X.Length;    int n = Y.Length;    Console.Write("Length of LCS: " +                    lcs(X, Y, m, n));}} // This code is contributed by 29AjayKumar
<?php// A Naive PHP recursive implementation// of LCS of two strings // Returns length of LCS for// X[0..m-1], Y[0..n-1]function lcs($X, $Y, $m, $n){    if ($m == 0 || $n == 0)        return 0;     if ($X[$m - 1] == $Y[$n - 1])        return 1 + lcs($X, $Y,                       $m - 1, $n - 1);    else        return max(lcs($X, $Y, $m, $n - 1),                   lcs($X, $Y, $m - 1, $n));} // Driver Code$X = "AGGTAB";$Y = "GXTXAYB"; // Find the length of string$m = strlen($X);$n = strlen($Y); echo "Length of LCS: " . lcs($X, $Y, $m, $n); // This code is contributed by ita_c?>
<script>// A Naive Javascript recursive implementation// of LCS of two strings         // Returns length of LCS for X[0..m-1], Y[0..n-1]    function lcs(X,Y,m,n)    {        if (m == 0 || n == 0) {            return 0;        }           if (X[m-1] == Y[n-1]) {            return 1 + lcs(X, Y, m - 1, n - 1);        } else {            return Math.max(lcs(X, Y, m, n - 1),                    lcs(X, Y, m - 1, n));        }    }         // Driver Code    let X = "AGGTAB";    let Y = "GXTXAYB";    // Find the length of String    let m = X.length;    let n = Y.length;    document.write("Length of LCS: " + lcs(X, Y, m, n));  // This code is contributed by rag2127</script>
Length of LCS: 4
 
Dynamic Programming using Memoization
Considering the above implementation, the following is a partial recursion tree for input strings “AXYT” and “AYZX”  
                       lcs("AXYT", "AYZX")
                       /                 \
         lcs("AXY", "AYZX")            lcs("AXYT", "AYZ")
         /           \                   /               \
lcs("AX", "AYZX") lcs("AXY", "AYZ")   lcs("AXY", "AYZ") lcs("AXYT", "AY")
In the above partial recursion tree, lcs(“AXY”, “AYZ”) are being solved twice. On drawing the complete recursion tree, it has been observed that there are many subproblems that are solved again and again. So this problem has Overlapping Substructure property and recomputation of same subproblems can be avoided by either using Memoization or Tabulation. The tabulation method has been discussed here. A common point of observation to use memoization in the recursive code will be the two non-constant arguments M and N in every function call. The function has 4 arguments, but 2 arguments are constant which does not affect the Memoization. The repetitive calls occur for N and M which have been called previously. Following the below steps will help us to write the DP solution using memoization. 
Use a 2-D array to store the computed lcs(m, n) value at arr[m-1][n-1] as the string index starts from 0.
Whenever the function with the same argument m and n are called again, do not perform any further recursive call and return arr[m-1][n-1] as the previous computation of the lcs(m, n) has already been stored in arr[m-1][n-1], hence reducing the recursive calls that happen more than once.
Below is the implementation of the above approach: 
C++
Java
Python3
C#
Javascript
// C++ program to memoize// recursive implementation of LCS problem#include <bits/stdc++.h>using namespace std; const int maximum = 1000; // Returns length of LCS for X[0..m-1], Y[0..n-1] */// memoization applied in recursive solutionint lcs(string X, string Y, int m, int n, int dp[][maximum]){    // base case    if (m == 0 || n == 0)        return 0;     // if the same state has already been    // computed    if (dp[m - 1][n - 1] != -1)        return dp[m - 1][n - 1];     // if equal, then we store the value of the    // function call    if (X[m - 1] == Y[n - 1]) {         // store it in arr to avoid further repetitive        // work in future function calls        dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);         return dp[m - 1][n - 1];    }    else {         // store it in arr to avoid further repetitive        // work in future function calls        dp[m - 1][n - 1] = max(lcs(X, Y, m, n - 1, dp),                               lcs(X, Y, m - 1, n, dp));         return dp[m - 1][n - 1];    }} // Driver Codeint main(){     string X = "AGGTAB";    string Y = "GXTXAYB";    int m = X.length();    int n = Y.length();     int dp[m][maximum];     // assign -1 to all positions    memset(dp, -1, sizeof(dp));     cout << "Length of LCS: " << lcs(X, Y, m, n, dp);     return 0;}
import java.util.Arrays; // Java program to memoize// recursive implementation of LCS problemclass GFG {     static final int maximum = 1000; // Returns length of LCS for X[0..m-1], Y[0..n-1] */// memoization applied in recursive solution    static int lcs(String X, String Y, int m, int n, int dp[][]) {        // base case        if (m == 0 || n == 0) {            return 0;        }         // if the same state has already been        // computed        if (dp[m - 1][n - 1] != -1) {            return dp[m - 1][n - 1];        }         // if equal, then we store the value of the        // function call        if (X.charAt(m - 1) == Y.charAt(n - 1)) {             // store it in arr to avoid further repetitive            // work in future function calls            dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);             return dp[m - 1][n - 1];        } else {             // store it in arr to avoid further repetitive            // work in future function calls            dp[m - 1][n - 1] = Math.max(lcs(X, Y, m, n - 1, dp),                    lcs(X, Y, m - 1, n, dp));             return dp[m - 1][n - 1];        }    } // Driver Code    public static void main(String[] args) {        String X = "AGGTAB";        String Y = "GXTXAYB";        int m = X.length();        int n = Y.length();         int dp[][] = new int[m][maximum];         // assign -1 to all positions        for (int[] row : dp) {            Arrays.fill(row, -1);        }         System.out.println("Length of LCS: " + lcs(X, Y, m, n, dp));    }}/* This Java code is contributed by 29AjayKumar*/
# Python3 program to memoize# recursive implementation of LCS problemmaximum = 1000 # Returns length of LCS for X[0..m-1], Y[0..n-1] */# memoization applied in recursive solutiondef lcs(X, Y, m, n, dp):         # base case    if (m == 0 or n == 0):        return 0     # if the same state has already been    # computed    if (dp[m - 1][n - 1] != -1):        return dp[m - 1][n - 1]     # if equal, then we store the value of the    # function call    if (X[m - 1] == Y[n - 1]):         # store it in arr to avoid further repetitive        # work in future function calls        dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp)         return dp[m - 1][n - 1]     else :         # store it in arr to avoid further repetitive        # work in future function calls        dp[m - 1][n - 1] = max(lcs(X, Y, m, n - 1, dp),                               lcs(X, Y, m - 1, n, dp))         return dp[m - 1][n - 1] # Driver CodeX = "AGGTAB"Y = "GXTXAYB"m = len(X)n = len(Y) dp = [[-1 for i in range(maximum)]          for i in range(m)] print("Length of LCS:", lcs(X, Y, m, n, dp)) # This code is contributed by Mohit Kumar
// C# program to memoize// recursive implementation of LCS problemusing System; class GFG{static readonly int maximum = 1000; // Returns length of LCS for// X[0..m-1], Y[0..n-1]// memoization applied in// recursive solutionstatic int lcs(String X, String Y,               int m, int n, int [,]dp){    // base case    if (m == 0 || n == 0)    {        return 0;    }     // if the same state has already been    // computed    if (dp[m - 1, n - 1] != -1)    {        return dp[m - 1, n - 1];    }     // if equal, then we store the value    // of the function call    if (X[m - 1] == Y[n - 1])    {         // store it in arr to avoid        // further repetitive work        // in future function calls        dp[m - 1,           n - 1] = 1 + lcs(X, Y, m - 1,                                  n - 1, dp);         return dp[m - 1, n - 1];    }    else    {         // store it in arr to avoid        // further repetitive work        // in future function calls        dp[m - 1,           n - 1] = Math.Max(lcs(X, Y, m, n - 1, dp),                             lcs(X, Y, m - 1, n, dp));         return dp[m - 1, n - 1];    }} // Driver Codepublic static void Main(String[] args){    String X = "AGGTAB";    String Y = "GXTXAYB";    int m = X.Length;    int n = Y.Length;     int [,]dp = new int[m, maximum];     // assign -1 to all positions    for(int i = 0; i < m; i++)    {        for(int j = 0; j < maximum; j++)        {            dp[i, j] = -1;        }    }    Console.WriteLine("Length of LCS: " +                    lcs(X, Y, m, n, dp));}} // This code is contributed by PrinciRaj1992
<script>// Javascript program to memoize// recursive implementation of LCS problem let maximum = 1000; // Returns length of LCS for X[0..m-1], Y[0..n-1] */// memoization applied in recursive solutionfunction lcs(X,Y,m,n,dp){    // base case        if (m == 0 || n == 0) {            return 0;        }          // if the same state has already been        // computed        if (dp[m - 1][n - 1] != -1) {            return dp[m - 1][n - 1];        }          // if equal, then we store the value of the        // function call        if (X[m-1] == Y[n-1]) {              // store it in arr to avoid further repetitive            // work in future function calls            dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);              return dp[m - 1][n - 1];        } else {              // store it in arr to avoid further repetitive            // work in future function calls            dp[m - 1][n - 1] = Math.max(lcs(X, Y, m, n - 1, dp),                    lcs(X, Y, m - 1, n, dp));              return dp[m - 1][n - 1];        }} // Driver Codelet X = "AGGTAB";let Y = "GXTXAYB";let m = X.length;let n = Y.length; let dp=new Array(m);for(let i=0;i<dp.length;i++){    dp[i]=new Array(maximum);    for(let j=0;j<dp[i].length;j++)    {        dp[i][j]=-1;    }} document.write("Length of LCS: " + lcs(X, Y, m, n, dp));       // This code is contributed by avanitrachhadiya2155</script>
Length of LCS: 4
 
Time Complexity: O(N * M), where N and M are lengths of the first and second string respectively. Auxiliary Space: (N * M) 
29AjayKumar
ukasp
mohit kumar 29
princiraj1992
rag2127
avanitrachhadiya2155
LCS
Memoization
Dynamic Programming
Strings
Strings
Dynamic Programming
LCS
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Optimal Substructure Property in Dynamic Programming | DP-2
Maximum sum such that no two elements are adjacent
Min Cost Path | DP-6
Maximum Subarray Sum using Divide and Conquer algorithm
Gold Mine Problem
Reverse a string in Java
Write a program to print all permutations of a given string
KMP Algorithm for Pattern Searching
C++ Data Types
Array of Strings in C++ (5 Different Ways to Create)