Skip to content
Related Articles

Related Articles

Longest Common Subsequence | DP using Memoization
  • Difficulty Level : Medium
  • Last Updated : 17 Dec, 2019

Given two strings s1 and s2, the task is to find the length of 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 term 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. Total possible combinations will be 2n. Hence recursive solution will take O(2n).



Optimal Substructure:

  • Let the input sequences be 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++




// 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 Code
int 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;
}


Java




// 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


Python3




# 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 Code
if __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


C#




// A Naive recursive C#implementation of 
// LCS of two strings 
using 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 Code 
public 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




<?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
?>


Output:

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”) is being solved twice. On drawing the complete recursion tree, it has been observed that there are many subproblems which 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 do 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 then once.

Below is the implementation of the above approach:

C++




// 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 solution
int 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 Code
int 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;
}


Java




import java.util.Arrays;
  
// Java program to memoize
// recursive implementation of LCS problem 
class 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




# Python3 program to memoize
# recursive implementation of LCS problem
maximum = 1000
  
# Returns length of LCS for X[0..m-1], Y[0..n-1] */
# memoization applied in recursive solution
def 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 Code
X = "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#




// C# program to memoize
// recursive implementation of LCS problem 
using 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 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[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 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 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


Output:

Length of LCS: 4

Time Complexity: O(N * M), where N and M is length of the first and second string respectively.
Auxiliary Space: (N * M)

Attention reader! Don’t stop learning now. Get hold of all the important DSA concepts with the DSA Self Paced Course at a student-friendly price and become industry ready.

My Personal Notes arrow_drop_up
Recommended Articles
Page :