[路飞]_前端算法第十七弹-剑指 Offer II 095. 最长公共子序列

116 阅读2分钟

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

给定两个字符串 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 。

最长公共子序列是一道典型的二维动态规划的问题。

假设字符串text1和text2的长度分别为m和n,那么我们就创建一个m+1行n+1列的二维数组dp,其中dp[i][j]表示text1[0:i]和text2[0:j]的最长公共子序列。

即text1从0到i的子序列和text从0到j的子序列的最长公共子序列。

动态规划在我的理解就是将一个大的问题分成最小单元的子问题,进行累加对比。然后再通过上一次对比的值和这一次对比的值,计算出此时对比的最终值。就拿这道最长公共子序列为例。

text1 = "abcde", text2 = "ace";

此时我们会建立一个二维数组,行列分别为text1.length+1,text2.length+1,dp:

[0,0,0,0,
 0,0,0,0,
 0,0,0,0,
 0,0,0,0,
 0,0,0,0]

我们将text1进行循环,得到了其最小单元

for(let i = 1;i<=m;i++){
	let c1 = text1[i-1]
}

由于当i=0的时候,为我们设置的初始值0,代表空字符串情况下,和任何字符串对比,最长公共子序列都是零。

所以这就是为什么长度为m+1,所以我们从i=1,开始遍历,到遍历完m即m+1为止。

若要能获取到text1的第i位,则需要text1[i-1]。

第二步则是对text2进行遍历,和c1进行对比。

for(let i = 1;i<=m;i++){
	let c1 = text1[i-1]
	for(let j = 1;j<=n;j++){
		let c2 = text2[j-1]
	}
}

将两个字符串都进行的最小化的遍历之后,就到了对比环节。

for (let i = 1; i <= m; i++) {
    const c1 = text1[i - 1];
    for (let j = 1; j <= n; j++) {
      const c2 = text2[j - 1];
      if (c1 == c2) {
        dp[i][j] = dp[i - 1][j - 1] + 1
      } else {
        dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j])
      }
    }
  }

如果c1=c2,那我们只需要在上次对比的结果上再加一即可,比如当text1和text2的第一个值相等,那么dp[1][1] = 1。

如果c1≠c2,那么就需要找到dp[i][j - 1], dp[i - 1][j]中最大的那一个,这两个值代表的是长度i-1的text1和长度j的text2进行对比的最大值与长度i的text1和长度j-1的text2的最大值中的最大值,代表现阶段的最大值,以text1 = "abcde", text2 = "ace"为例,假如此时i=3,j=3 此时的c1 = c,c2 = e,二者不相等,那就要对比i-1和j长度即"ab"和"ace"的长度1与i和j-1长度即"abc"和"ac"的长度2,中的最大值2。dp[3][3]=2。当text1和text2都遍历完,最后的dp[m][n]的值则为最终结果。

以下图为例

如果每次对比的结果相等,就在左上方的值加一,如果不等,就在左边和上边中选最大值进行赋值,知道二维数组全部遍历完毕,右下角的值为最终结果。

全部分析完毕后,最终代码为

var longestCommonSubsequence = function(text1, text2) {
    const m = text1.length, n = text2.length;
    const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
    for (let i = 1; i <= m; i++) {
        const c1 = text1[i - 1];
        for (let j = 1; j <= n; j++) {
            const c2 = text2[j - 1];
            if (c1 === c2) {
                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];
};