武汉中小企业网站制作公司本网站三天换一次域名
143. 最长公共子序列
方法一:记忆化搜索
一、算法逻辑(每一步思路)
❓ 问题描述:
给定两个字符串 text1
和 text2
,返回它们的最长公共子序列的长度。
公共子序列:在两个字符串中都出现过,且相对顺序一致,但不要求连续。
✅ 解题思路(递归 + 记忆化)
1. 状态定义:
定义 dfs(i, j)
表示:
- 在
text1[0..i]
和text2[0..j]
中的最长公共子序列的长度。
2. 状态转移:
if text1[i] == text2[j]:dfs(i, j) = dfs(i-1, j-1) + 1
else:dfs(i, j) = max(dfs(i-1, j), dfs(i, j-1))
解释:
- 如果当前两个字符相等,则这个字符可以作为公共子序列的一部分,答案 +1;
- 如果不相等,我们可以选择跳过一个字符,取两种可能中的最大值。
3. 终止条件(Base Case):
if i < 0 or j < 0:return 0
表示有一个字符串已经空了,公共子序列长度为 0。
4. 返回值:
最终返回 dfs(n - 1, m - 1)
,即原字符串全部范围的 LCS 长度。
二、算法核心点
✅ 核心技巧:记忆化搜索 + 状态递归
- 通过
@cache
进行记忆化,避免重复计算(如果不用 cache,会超时); - 状态空间是
(i, j)
二维的。
这等价于二维动态规划的形式,但使用递归写法逻辑更清晰。
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:n, m = len(text1), len(text2)@cachedef dfs(i:int, j:int) -> int:if i<0 or j<0:return 0if text1[i] == text2[j]:return dfs(i-1, j-1)+1return max(dfs(i-1, j), dfs(i, j-1))return dfs(n-1, m-1)
三、复杂度分析
- 时间复杂度:O(n * m)
每个(i, j)
状态只会被访问一次。 - 空间复杂度:O(n * m)
用于保存缓存结果(memo)和递归栈深度。
✅ 举个例子
输入:
text1 = "abcde"
text2 = "ace"
执行过程:
"abcde" 与 "ace"
比较 'e' 和 'e' -> 相等 => +1,继续比较 'd' 与 'c' ...
最终返回公共子序列是 "ace",长度为 3。
✅ 总结表
维度 | 内容 |
✅ 思路逻辑 | 定义 dfs(i, j):text1[0..i] 与 text2[0..j] 的 LCS 长度 |
✅ 状态转移 | 相等则 +1,否者取左/上两种子问题最大值 |
✅ 技巧 | 递归 + 记忆化(Top-down DP) |
✅ 时间复杂度 | O(n * m) |
✅ 空间复杂度 | O(n * m) |
方法一:1:1 翻译成递推
✅ 一、核心思路解析
❓ 问题:
给定两个字符串 text1
和 text2
,找出它们的最长公共子序列的长度。
✅ 解题思路(二维 DP)
1. 状态定义:
定义二维数组 f
:
f[i][j]
表示text1[0..i-1]
与text2[0..j-1]
的最长公共子序列长度。
注意这里的偏移:我们实际比较的是字符串的前 i
和前 j
个字符,但 f
的下标是从 1 开始的(第 0 行/列为 base case)。
2. 状态转移方程:
if text1[i] == text2[j]:f[i+1][j+1] = f[i][j] + 1
else:f[i+1][j+1] = max(f[i][j+1], f[i+1][j])
解释:
- 如果两个字符相等,则这个字符可以加入公共子序列。
- 否则,继承“去掉当前一个字符”的最优解。
3. 初始化:
f = [[0] * (m+1) for _ in range(n+1)]
第一行和第一列代表一个字符串为空时,LCS 长度为 0。
4. 最终答案:
return f[n][m]
表示完整的两个字符串的 LCS 长度。
class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:n, m = len(text1), len(text2)f = [[0]*(m+1) for _ in range(n+1)]for i,x in enumerate(text1):for j,y in enumerate(text2):if x==y:f[i+1][j+1] = f[i][j]+1else:f[i+1][j+1] = max(f[i][j+1], f[i+1][j])return f[n][m]
✅ 二、图解对比示意
假设输入:
text1 = "abcde"
text2 = "ace"
DP 表格 f:
"" | a | c | e | |
"" | 0 | 0 | 0 | 0 |
a | 0 | 1 | 1 | 1 |
b | 0 | 1 | 1 | 1 |
c | 0 | 1 | 2 | 2 |
d | 0 | 1 | 2 | 2 |
e | 0 | 1 | 2 | 3 |
结果是 3,对应 LCS 为 "ace"
。
✅ 三、复杂度分析
- 时间复杂度:O(n * m)
两层循环枚举所有子串组合。 - 空间复杂度:O(n * m)
使用了二维数组f[n+1][m+1]
。
可进一步优化为 O(m) 空间,用一维滚动数组,但二维形式更直观清晰。
✅ 总结表格
项目 | 内容 |
状态定义 |
|
状态转移 | 如果相等: |
初始值 |
|
返回值 |
|
时间复杂度 |
|
空间复杂度 |
|