最长公共子序列-动态规划法(go+java)

47 阅读3分钟

题目

给定两个字符串 s 和 t,返回这两个字符串的最长公共子序列的长度。 示例:

输入:s = "abcde", t = "ace"

输出:3

解释:最长公共子序列是 "ace",它的长度是 3。



输入:s = "abc", t = "def"

输出:0

解释:两个字符串没有公共子序列,返回 0。

动态规划的思路:

动态规划的状态定义:

dp[i][j]表示s[0:i-1]和t[0:j-1]的最长公共子序列的长度。

动态规划的状态转移:

当s[i-1] = t[j-1]时,dp[i][j] = dp[i-1][j-1] + 1。


当s[i-1] ≠ t[j-1]时,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

image.png

注意,由于dp[i][j]只与dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]有关,所以我们可以将状态转移方程中的i-1和j-1去掉,只使用一维数组进行状态转移。

golang 代码实现

func longestCommonSubsequence(text1 string, text2 string) int {

    m, n := len(text1), len(text2)

    dp := make([]int, n+1)

    for i := 1; i <= m; i++ {

        pre := 0 // dp[i-1][j-1]

        for j := 1; j <= n; j++ {

            temp := dp[j] // dp[i-1][j]

            if text1[i-1] == text2[j-1] {

                dp[j] = pre + 1

            } else {

                dp[j] = max(dp[j], dp[j-1])

            }

            pre = temp

        }

    }

    return dp[n]

}

 

func max(a, b int) int {

    if a > b {

        return a

    }

    return b

}

其中dp[i][j]表示text1[0:i-1]和text2[0:j-1]的最长公共子序列的长度,dp[i][j]的值可以通过dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]的值进行推导。因为dp[i][j]只与dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]有关,所以我们可以将状态转移方程中的i-1和j-1去掉,只使用一维数组进行状态转移。具体实现中,我们使用一个一维数组dp记录dp[i][j]的值,使用pre和temp变量记录dp[i-1][j-1]和dp[i-1][j]的值。

java 实现

public class LongestCommonSubsequence {

    public static void main(String[] args) {

        String s1 = "abcdefg";

        String s2 = "aceg";

        System.out.println(longestCommonSubsequence(s1, s2));

    }

 

    public static int longestCommonSubsequence(String s1, String s2) {

        int m = s1.length();

        int n = s2.length();

        int[][] dp = new int[m+1][n+1];

 

        // 初始化第一行和第一列

        for (int i = 0; i <= m; i++) {

            dp[i][0] = 0;

        }

        for (int j = 0; j <= n; j++) {

            dp[0][j] = 0;

        }

 

        // 动态规划计算

        for (int i = 1; i <= m; i++) {

            for (int j = 1; j <= n; j++) {

                if (s1.charAt(i-1) == s2.charAt(j-1)) {

                    dp[i][j] = dp[i-1][j-1] + 1;

                } else {

                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);

                }

            }

        }

 

        return dp[m][n];

    }

}

一个二维数组 dp 来记录最长公共子序列的长度,其中 dp[i][j] 表示字符串 s1 的前 i 个字符和字符串 s2 的前 j 个字符的最长公共子序列的长度。在计算 dp[i][j] 时,分两种情况:

当 s1 的第 i 个字符和 s2 的第 j 个字符相同时,说明这两个字符可以构成最长公共子序列的一部分,因此 dp[i][j] 的值应该等于 dp[i-1][j-1] + 1。

当 s1 的第 i 个字符和 s2 的第 j 个字符不同时,说明这两个字符不可能同时出现在最长公共子序列中,因此 dp[i][j] 的值应该等于 dp[i-1][j] 和 dp[i][j-1] 中的最大值。

最终,dp[m][n] 即为字符串 s1 和 s2 的最长公共子序列的长度。