📜  最长公共子序列 | DP 使用记忆化

📅  最后修改于: 2021-09-17 16:12:42             🧑  作者: 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


Javascript


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


Javascript


输出:
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”) 被求解了两次。在绘制完整的递归树时,已经观察到有许多子问题被一次又一次地解决。所以这个问题具有重叠子结构属性,可以通过使用MemoizationTabulation来避免相同子问题的重新计算。这里已经讨论了制表方法。
在递归代码中使用记忆化的一个常见观察点是每个函数调用中的两个非常量参数 M 和 N。该函数有 4 个参数,但 2 个参数是常量,不影响记忆。重复调用发生在之前调用过的 N 和 M 上。遵循以下步骤将帮助我们使用 memoization 编写 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*/

蟒蛇3

# 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

Javascript


输出:
Length of LCS: 4

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

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程