动态规划
● 392.判断子序列
● 115.不同的子序列
编辑距离
对于s和t,用dp[i][j]表示s中前i个元素和t中前j个元素的所求关系。
判断s[i - 1]和t[j - 1]:均认为是第一次比较该元素,因为比较完成后该值会存储,故若两者相等,则dp[i][j]= f(dp[i - 1][j - 1])
若两者不相等,对s或t执行操作,以编辑距离为例,s或t均可执行增删替换三种操作,可分为s添加元素(相当于t删除元素)、s删除元素、s替换(s和t均删除元素)三种情况。以第一种s添加元素为例,添加元素,则s中前i个元素与t中前j-1个元素已匹配,添加元素操作代码中体现为+1;第二种s删除元素,是为了和t的第j个(下标j-1)元素匹配,则删除后,s中前i-1个元素与t中前j个元素已匹配,删除元素操作代码中体现为+1;第三种,两者均删除元素,相当于s中前i-1个元素和t中前j-1 个元素已匹配,两者的删除认为占操作数1。
重要:
子序列问题,子序列是不改变的,回退操作在长序列中发生,递推公式相应下标来自长序列
而此处的回退可理解为长序列的删除操作,推到编辑距离,区别在于,编辑距离的:1.两个序列都可以操作;2.删除、替换(两个序列均删除)、添加(另一个序列的删除),多种操作,均可转换为某一序列的删除,问题简化完成
392.判断子序列
题目描述
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
示例:
输入:s = “abc”, t = “ahbgdc”
输出:true
思路
1.dp数组含义
dp[i][j]指s中前i个字符和t中前j个字符的最大重复字符个数
目标:dp[s.size()][t.size()] == s.size()
2.dp递推公式
- s[i - 1] == t[j - 1]时:dp[i][j] = dp[i -1][j -1] + 1
- s[i - 1] != t[j - 1]时:dp[i][j] = max (dp[i -1][j], dp[i][j - 1]) = dp[i][j - 1]
由于想判断s是t的子串,此处相当于t要删除t[j - 1]元素继续匹配,则最大匹配长度从dp[i][j-1]得到
t是可操作的,对应代码是 j
3.dp初始化
dp[0][j]——s中前0个元素和t中任意长度的重复字符数均为0
dp[i][0]——t中前0个字符和s中任意长度的重复字符数均为0
代码
// dp[i][j]:s中前i个 和 t中前j个 重复字符的最大数目
// 1.s[i - 1] == t[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1;
// 2.s[i - 1] != t[j - 1]:dp[i][j] = dp[i][j - 1];//相当于删除t中t[j - 1],继续向后
// 初始化:s或t任前0个字符,与另一个串的匹配长度均为0
class Solution {
public:
bool isSubsequence(string s, string t) {
int m = s.size();
int n = t.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (s[i - 1] == t[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
//dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
dp[i][j] = dp[i][j - 1];
}
}
}
if (dp[m][n] == s.size())
{
return true;
}
return false;
}
};
115.不同的子序列
题目描述
给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
题目数据保证答案符合 32 位带符号整数范围。
思路
1.dp含义
dp[i][j]表示s中前i个元素、t中前j个元素,此时在s的子序列中t出现的个数
2.递推公式
i , j 同时移动,s[i - 1] == t [j - 1]时,s 中 t 出现的次数取决于dp[i - 1][j - 1];考虑重复出现的字母,i 需要回退,累加之前的个数;
s[i - 1] != t [j - 1]时,s 中 i 移动,含义为 s 中前 i 个元素 出现 t 中前 j 个元素的个数等于 s 中前 i - 1个元素 出现 t 中前 j 个元素的个数【显然,因为这个元素不匹配,所以回退,回退操作发生在s中】
t是可操作的,对应代码是 j
3.dp初始化
相当于每个集合都有一个空集? s中出现 t 空(空集)的数目是1。
代码
// dp[i][j]:s的前i个字符 t中前j个字符 前者中后者出现的个数
class Solution {
public:
int numDistinct(string s, string t) {
int m = s.size();
int n = t.size();
vector<vector<int>> dp(m + 1,vector<int>(n + 1));
for (int i = 0; i <= m; i++)
{
dp[i][0] = 1;
}
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
if (s[i - 1] == t[j - 1])
{
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
}
else
{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m][n];
}
};