跳至主要內容

1143. Longest Common Subsequence


1143. Longest Common Subsequenceopen in new window

🟠   🔖  字符串 动态规划  🔗 LeetCodeopen in new window

题目

Given two strings text1 and text2, return _the length of their longest common subsequence. _If there is no common subsequence , return 0.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

  • For example, "ace" is a subsequence of "abcde".

A common subsequence of two strings is a subsequence that is common to both strings.

Example 1:

Input: text1 = "abcde", text2 = "ace"

Output: 3

Explanation: The longest common subsequence is "ace" and its length is 3.

Example 2:

Input: text1 = "abc", text2 = "abc"

Output: 3

Explanation: The longest common subsequence is "abc" and its length is 3.

Example 3:

Input: text1 = "abc", text2 = "def"

Output: 0

Explanation: There is no such common subsequence, so the result is 0.

Constraints:

  • 1 <= text1.length, text2.length <= 1000
  • text1 and text2 consist of only lowercase English characters.

题目大意

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

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

  • 例如,"ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

text1text2 仅由小写英文字符组成。

解题思路

思路一:动态规划-递归

使用递归来解题的思路如下:

  • 定义一个递归函数 helper(i, j)ij 代表两个字符串的下标,函数返回最长公共子序列的长度;

  • base case 是当 ij 等于 -1,即其中一个字符串为空,最长公共子序列的长度为 0 ;

  • 用一个数组 dp 记录子问题的结果,避免重复计算;

  • 如果 dp[i][j] 已经存在,则直接返回,否则就递归计算:

    • s1[i] == s2[j],说明当前字符相同,最长公共子序列的长度可以在之前的基础上加 1dp[i][j] = 1 + helper(i - 1, j - 1)
    • 否则说明当前字符不同,最长公共子序列的长度为两个字符串中分别去掉当前字符后的最大值:dp[i][j] = Math.max(helper(i, j - 1), helper(i - 1, j))
      • dp[i-1][j] 表示舍弃字符 s1[i]
      • dp[i][j-1] 表示舍弃字符 s2[j]
      • 谁能让最长公共子序列最长,就选择谁;
  • 最后调用递归函数即可。

思路二:动态规划-DP table

定义一个二维数组 dp , 其中 dp[i][j] 表示 text1[0...i-1]text2[0...j-1] 的最长公共子序列的长度。

dp[..][0]dp[0][..] 对应 base case,即任何一个字符串为空时,最长公共子序列长度为 0

状态转移方程如下:

  • s1[i-1] === s2[j-1],那么这个字符一定在最长公共子序列中:dp[i][j] = 1 + dp[i-1][j-1]
  • 否则,这两个字符至少有一个不在最长公共子序列中,需要丢弃一个。取二者的最大值:dp[i][j] = max(dp[i-1][j], dp[i][j-1])

其中,dp[i-1][j] 表示舍弃字符 s1[i]dp[i][j-1] 表示舍弃字符 s2[j],谁能让最长公共子序列最长,就选择谁。

DP table 和递归的思路大致相同,唯一不同的是,DP table 是自底向上求解,递归解法是自顶向下求解。递归函数的 base case 是 i, j 等于 -1,而 DP table 的数组索引至少是 0,所以 DP table 数组会偏移一位。

动态规划数组的大小为 (m+1) * (n+1),因此时间复杂度为 O(mn) 空间复杂度也为 O(mn)

代码

动态规划-递归
/**
 * @param {string} text1
 * @param {string} text2
 * @return {number}
 */
var longestCommonSubsequence = function (text1, text2) {
	const m = text1.length;
	const n = text2.length;

	// 记录子问题的计算结果,避免重复计算
	let dp = new Array(m).fill(-1).map((i) => new Array(n).fill(-1));

	const helper = (i, j) => {
		// 递归的终止条件
		if (i == -1 || j == -1) return 0;

		// 避免重复计算子问题
		if (dp[i][j] != -1) return dp[i][j];

		// 状态转移
		if (text1.charAt(i) == text2.charAt(j)) {
			dp[i][j] = 1 + helper(i - 1, j - 1);
		} else {
			dp[i][j] = Math.max(helper(i, j - 1), helper(i - 1, j));
		}

		return dp[i][j];
	};

	// 调用递归函数
	return helper(m - 1, n - 1);
};

相关题目

相关题目
- [516. 最长回文子序列](https://leetcode.com/problems/longest-palindromic-subsequence)
- [583. 两个字符串的删除操作](https://leetcode.com/problems/delete-operation-for-two-strings)
- [1092. 最短公共超序列](https://leetcode.com/problems/shortest-common-supersequence)
- [2207. 字符串中最多数目的子字符串](https://leetcode.com/problems/maximize-number-of-subsequences-in-a-string)
- [2565. 最少得分子序列](https://leetcode.com/problems/subsequence-with-the-minimum-score)