点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃
1.最长公共子序列
题目链接:1143. 最长公共子序列
题目描述:
算法原理:
1.状态表示
这道题是遇到新的类型的题,两个字符串的dp问题。并且求的是最长公共子序列,我们很多题的经验就是从这道题来到。我们先总结一下这个经验。
经验 + 题目要求
- 选取第一个字符串 [0, i] 区间以及第二个字符串 [0, j] 区间作为研究对象
- 根据题目要求,确定状态表示
先以这两段区间来研究,这道题研究的是最长公共子序列。也就是说先看看上面s1这个区间所有子序列,以及下面s2这个区间所有子序列,其中的最长子序列。
dp[i][j] 表示:s1 的 [0, i] 区间以及 s2 的[0, j] 区间内所有子序列中,最长公共子序列的长度。
如果以某个位置为结尾所有子序列,还有在去找子序列,时间复杂O(N^4),而我们这里的子序列是所有的子序列,比如是说 [0,i] 区间所有的子序列。这个子序列既包含以i为结尾,也包含不以i为结尾的。
2.状态转移方程
根据最后一个位置的情况,分情况讨论
如果最后一个位置字符相等 s[i] == s[j]
是不是可以先在s1 [ 0 , i-1] 和 s2 [0 , j - 1]区间内所有子序列找最长公共子序列长度 然后 加上 1就行了
如果最后一个位置字符相等 s[i] != s[j]
那么最长公共子序列一定不是以 i j 位置为结尾,也就是说最长公共子序列一定不是同时选择 i 和 j 位置。那可以这样选
- s1[0, i - 1],s2[0, j] ,不选 i
- s1[0, i],s2[0, j -1] ,不选 j
- s1[0, i - 1],s2[0, j - 1], i j都不选
我们一般找状态转移方程要把所有情况不重不漏都找完,情况1 和 情况2 都包含情况3。但是注意我们的状态表示是最长公共子序列的长度,我们要找的是多种情况的max。所有这里的重复不会影响。但是如果要找的是公共子序列的个数,那就有问题了。遇到了再说。还有情况1和情况2包括情况3,所以填表的时候情况3就不用考虑了。
3.初始化
关于字符串的 dp 问题: 空串是有研究意义的
比如 s1 选一个空串,s2 选一个空串,其实也是一个公共子序列,不过公共子序列长度为0。
引入空串的概念之后,会方便我们的初始化
s1 [0,i] 区间,i最小0,最差就是 [0,0]是一定有一个字符的,如何表示空串呢?可以在原始 dp 表上多来一行一列,第一行表示第一个字符串为空,第二行表示第二个字符串为空。
并且填表也不会越界了。而且第一行表示第一个字符串为空,所以最长公共子序为0,第一列表示第二个字符串为空,所以最长公共子序也是0。
引入辅助节点要注意两个注意事项:
- 里面的值要保证我们后序填表是正确的
- 下标的映射关系
因为多开一行一列相当于dp表向右下移动一位,如果要回原表肯定是要 i-1,j-1。但是在字符串这里,我们可以在每个字符串前面加一个 ‘ ’,然后字符串有效字符就从1开始计数了。这个时候填表 1,1位置的时候就是对应字符串1,1位置。
4.填表顺序
填 i j 会遇到左上,上,左,因此从上往下填写每一行,每一行从左往右
5.返回值
dp[i][j] 表示:s1 的 [0, i] 区间以及 s2 的[0, j] 区间内所有子序列中,最长公共子序列的长度,而我们要的是s1,s2整个字符串的最长子序列的长度。因此 返回 dp[m][n]
class Solution {
public:
int longestCommonSubsequence(string s1, string s2) {
// 1.创建 dp 表
// 2.初始化
// 3.填表
// 4.返回值
int m = s1.size(), n = s2.size();
s1 = ' ' + s1, s2 = ' ' + s2;// 处理下标映射关系
vector<vector<int>> dp(m + 1, vector<int>(n + 1));// 建表 + 初始化
for(int i = 1; i <= m; ++i) // 从上往下每⼀⾏
for(int j = 1; j <= n; ++j)// 每⼀⾏从左往右
//if(s1[i - 1] == s2[j - 1])
if(s1[i] == s2[j])
dp[i][j] = dp