leetcode1143.最长公共子序列

一.题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace” ,它的长度为 3 。
示例 2:

输入:text1 = “abc”, text2 = “abc”
输出:3
解释:最长公共子序列是 “abc” ,它的长度为 3 。
示例 3:

输入:text1 = “abc”, text2 = “def”
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:

1 <= text1.length, text2.length <= 1000
text1 和 text2 仅由小写英文字符组成。

二.题目解析

1.动态规划

public int longestCommonSubsequence(String text1, String text2) {
        /*动态规划
        状态定义:dp[i][j]表示text1的长度为i的前缀字符串与text2的长度为j的前缀字符串的最长公共子序列
        状态转移方程:若相等则dp[i][j] = dp[i - 1][j - 1] + 1;
        若不相等则dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
        (原因:虽然尾巴字符不同, 但一个字符串的尾巴字符可能和
        另一字符串的前面的某个字符相同,导致最长公共子序列得到扩展。)
        空间复杂度:O(MN),空间复杂度:O(MN)
        * */
        int[][] dp = new int[text1.length() + 1][text2.length() + 1];
        //下标从1开始取是因为防止数组越界,(i=0或者j=0代表空字符串与另一个字符串取公共子序列,
        // 所以结果是0,又因为dp数组本身初始化0,所以无需处理i=0或者j=0的情况
        for (int i = 1; i <= text1.length(); i++) {
            for (int j = 1; j <= text2.length(); j++) {
                if(text1.charAt(i - 1) == text2.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[text1.length()][text2.length()];
    }

leetcode1143.最长公共子序列_第1张图片
2.状态压缩

public int longestCommonSubsequence1(String text1, String text2) {
        /*由解法一可知每个格子内的值会依赖他的左边,上边和左上边,和其他的值无关,因此可考虑状态压缩
        状态定义:dp[j]表示text1的不同长度的前缀字符串与text2的长度为j的前缀字符串的最长公共子序列,
        在遍历过程中i不断增加,dp[j]不断被覆盖,直到i遍历完,得到最终的最长公共子序列
        状态转移方程:若相等则dp[j] = pre + 1;(在生成这个格子的值的时候,下个格子的左上边格子的值被覆盖,
        所以用变量pre保存一下
        若不相等则dp[j] = Math.max(dp[j],dp[j - 1]);
        空间复杂度:O(MN),空间复杂度:O(N)
        如下:每次改变的是[]中的一列值
         m   h   u   z   t   nums1数组
    0    0    0   0   0   0
s   0   [0]   0   0   0   0
m   0   [1]   1   1   1   1
r   0   [1]   1   1   1   1
z   0   [1]   1   1   2   2
nums2数组      * */
        int[] dp = new int[text2.length() + 1];
        //pre初始化是0
        int pre = 0,temp = 0;
        //下标从1开始取是因为防止数组越界,(i=0或者j=0代表空字符串与另一个字符串取公共子序列,
        // 所以结果是0,又因为dp数组本身初始化0,所以无需处理i=0或者j=0的情况
        for (int i = 1; i <= text1.length(); i++) {
            for (int j = 1; j <= text2.length(); j++) {
                //临时变量保存此时的dp[j](即下个格子的左上边的值)
                temp = dp[j];
                if(text1.charAt(i - 1) == text2.charAt(j - 1)){
                    dp[j] = pre + 1;
                }else{
                    dp[j] = Math.max(dp[j],dp[j - 1]);
                }
                //更新pre
                pre = temp;
            }
            //一趟列值被更新后,pre重新变为0(j=1时左上边的格子在第一行,第一行的值都是0)
            pre = 0;
        }
        return dp[text2.length()];
    }

leetcode1143.最长公共子序列_第2张图片
参考文章:
https://writings.sh/post/algorithm-longest-common-substring-and-longest-common-subsequence#%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97

你可能感兴趣的:(leetcode,动态规划,状态压缩,前缀字符串)