📜  最长公共子序列| DP使用备忘

📅  最后修改于: 2021-05-04 08:44:13             🧑  作者: Mango

给定两个字符串s1和s2,任务是找到两个字符串存在的最长公共子序列的长度。

例子:

这个问题的幼稚解决方案是生成两个给定序列的所有子序列,并找到最长的匹配子序列。该解决方案在时间复杂度方面是指数级的。该问题的一般递归解决方案是生成两个给定序列的所有子序列,并找到最长的匹配子序列。总共可能的组合为2 n 。因此,递归解将采用O(2 n )

最佳子结构:

  • 令输入序列分别为长度[m]和[n]的X [0..m-1]和Y [0..n-1]。并令L(X [0..m-1],Y [0..n-1])为两个序列X和Y的LCS的长度。以下是L(X [0 … m-1],Y [0..n-1])。
  • 如果两个序列的最后一个字符都匹配(或X [m-1] == Y [n-1]),则L(X [0..m-1],Y [0..n-1])= 1 + L(X [0..m-2],Y [0..n-2])
  • 如果两个序列的最后一个字符都不匹配(或X [m-1]!= Y [n-1]),则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])

下面给出的是LCS问题的递归解决方案:

C++
// A Naive C++ recursive implementation
// of LCS of two strings
#include 
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


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


输出:
Length of LCS: 4

使用记忆的动态编程

考虑到以上实现,以下是输入字符串“ AXYT”“ AYZX”的部分递归树

lcs("AXYT", "AYZX")
                       /                 \
         lcs("AXY", "AYZX")            lcs("AXYT", "AYZ")
         /           \                   /               \
lcs("AX", "AYZX") lcs("AXY", "AYZ")   lcs("AXY", "AYZ") lcs("AXYT", "AY")

在上面的部分递归树中, lcs(“ AXY”,“ AYZ”)被求解两次。在绘制完整的递归树时,已经观察到有很多子问题可以一次又一次地解决。因此,此问题具有“重叠子结构”属性,可以通过使用“记忆化”或“制表”来避免相同子问题的重新计算。在此已经讨论了制表方法。

在递归代码中使用记忆的一个常见观察点是每个函数调用中的两个非恒定参数M和N。该函数具有4个自变量,但2个自变量是常量,不会影响备注。重复调用发生在先前已调用的N和M上。遵循以下步骤将帮助我们使用备忘录编写DP解决方案。

  • 使用二维数组将计算的lcs(m,n)值存储在arr [m-1] [n-1],因为字符串索引从0开始。
  • 每当再次调用具有相同参数m和n的函数时,请勿执行任何进一步的递归调用并返回arr [m-1] [n-1],因为先前已存储了lcs(m,n)的先前计算结果在arr [m-1] [n-1]中,因此减少了发生一次以上的递归调用。

下面是上述方法的实现:

C++

// C++ program to memoize
// recursive implementation of LCS problem
#include 
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
输出:
Length of LCS: 4

时间复杂度: O(N * M),其中N和M分别是第一和第二个字符串的长度。
辅助空间: (N * M)