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:
Implementation:
C++
#include <bits/stdc++.h>
using namespace std;
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));
}
int main()
{
string X = "AGGTAB" ;
string Y = "GXTXAYB" ;
int m = X.length();
int n = Y.length();
cout << "Length of LCS: " << lcs(X, Y, m, n);
return 0;
}
|
Java
class GFG {
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));
}
}
public static void main(String[] args) {
String X = "AGGTAB" ;
String Y = "GXTXAYB" ;
int m = X.length();
int n = Y.length();
System.out.println( "Length of LCS: " + lcs(X, Y, m, n));
}
}
|
Python3
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))
if __name__ = = '__main__' :
X = "AGGTAB"
Y = "GXTXAYB"
m = len (X)
n = len (Y)
print ( "Length of LCS:" ,
lcs(X, Y, m, n))
|
C#
using System;
class GFG
{
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));
}
}
public static void Main()
{
String X = "AGGTAB" ;
String Y = "GXTXAYB" ;
int m = X.Length;
int n = Y.Length;
Console.Write( "Length of LCS: " +
lcs(X, Y, m, n));
}
}
|
PHP
<?php
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 ));
}
$X = "AGGTAB" ;
$Y = "GXTXAYB" ;
$m = strlen ( $X );
$n = strlen ( $Y );
echo "Length of LCS: " . lcs( $X , $Y , $m , $n );
?>
|
Javascript
<script>
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));
}
}
let X = "AGGTAB" ;
let Y = "GXTXAYB" ;
let m = X.length;
let n = Y.length;
document.write( "Length of LCS: " + lcs(X, Y, m, n));
</script>
|
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++
#include <bits/stdc++.h>
using namespace std;
const int maximum = 1000;
int lcs(string X, string Y, int m, int n, int dp[][maximum])
{
if (m == 0 || n == 0)
return 0;
if (dp[m - 1][n - 1] != -1)
return dp[m - 1][n - 1];
if (X[m - 1] == Y[n - 1]) {
dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);
return dp[m - 1][n - 1];
}
else {
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];
}
}
int main()
{
string X = "AGGTAB" ;
string Y = "GXTXAYB" ;
int m = X.length();
int n = Y.length();
int dp[m][maximum];
memset (dp, -1, sizeof (dp));
cout << "Length of LCS: " << lcs(X, Y, m, n, dp);
return 0;
}
|
Java
import java.util.Arrays;
class GFG {
static final int maximum = 1000 ;
static int lcs(String X, String Y, int m, int n, int dp[][]) {
if (m == 0 || n == 0 ) {
return 0 ;
}
if (dp[m - 1 ][n - 1 ] != - 1 ) {
return dp[m - 1 ][n - 1 ];
}
if (X.charAt(m - 1 ) == Y.charAt(n - 1 )) {
dp[m - 1 ][n - 1 ] = 1 + lcs(X, Y, m - 1 , n - 1 , dp);
return dp[m - 1 ][n - 1 ];
} else {
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 ];
}
}
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];
for ( int [] row : dp) {
Arrays.fill(row, - 1 );
}
System.out.println( "Length of LCS: " + lcs(X, Y, m, n, dp));
}
}
|
Python3
maximum = 1000
def lcs(X, Y, m, n, dp):
if (m = = 0 or n = = 0 ):
return 0
if (dp[m - 1 ][n - 1 ] ! = - 1 ):
return dp[m - 1 ][n - 1 ]
if (X[m - 1 ] = = Y[n - 1 ]):
dp[m - 1 ][n - 1 ] = 1 + lcs(X, Y, m - 1 , n - 1 , dp)
return dp[m - 1 ][n - 1 ]
else :
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 ]
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))
|
C#
using System;
class GFG
{
static readonly int maximum = 1000;
static int lcs(String X, String Y,
int m, int n, int [,]dp)
{
if (m == 0 || n == 0)
{
return 0;
}
if (dp[m - 1, n - 1] != -1)
{
return dp[m - 1, n - 1];
}
if (X[m - 1] == Y[n - 1])
{
dp[m - 1,
n - 1] = 1 + lcs(X, Y, m - 1,
n - 1, dp);
return dp[m - 1, n - 1];
}
else
{
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];
}
}
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];
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));
}
}
|
Javascript
<script>
let maximum = 1000;
function lcs(X,Y,m,n,dp)
{
if (m == 0 || n == 0) {
return 0;
}
if (dp[m - 1][n - 1] != -1) {
return dp[m - 1][n - 1];
}
if (X[m-1] == Y[n-1]) {
dp[m - 1][n - 1] = 1 + lcs(X, Y, m - 1, n - 1, dp);
return dp[m - 1][n - 1];
} else {
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];
}
}
let 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));
</script>
|
complexity Analysis:
- Time Complexity: O(N * M), where N and M are lengths of the first and second string respectively.
- Auxiliary Space: (N * M)
Feeling lost in the world of random DSA topics, wasting time without progress? It's time for a change! Join our DSA course, where we'll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 geeks!