判断子序列
这道题可以双指针方法解决。
cpp
class Solution {
public:
bool isSubsequence(string s, string t) {
int s_index = 0;
for(int t_index = 0; t_index < t.size(); t_index++) {
if(s[s_index] == t[t_index]) {
s_index++;
}
}
return s_index == s.size();
}
};
用动态规划也是可解的,是简化的编辑距离问题。通过删除 t 中的字符得到 s,求解能否实现。dp[i][j] 表示 i-1 之前的字符串s子串和 j-1 之前的字符串t子串之间的最长公共子序列长度。这道题与上一道最长公共子序列是相同的。
cpp
class Solution {
public:
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
for(int i = 1; i <= s.size(); i++) {
for(int j = 1; j <= t.size(); j++) {
if(s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
// 因为是判断s是不是t的子序列,所以只删除t中的字符就可以,从dp[i][j-1]继承数值
dp[i][j] = dp[i][j - 1];
}
}
}
return dp[s.size()][t.size()] == s.size(); // 最长公共子序列与s一样长,说明s就是t的子序列
}
};
不同的子序列
这道题也是编辑距离类型的问题。本题中只涉及删除操作,对字符串 s 删除字符得到 t,求解操作方案的数目。
dp[i][j] 表示从字符串 s 下标 i-1 之前子串通过删除字符得到字符串 t 下标 j-1 之前子串的方案数目。
递推公式:针对这类问题我们可以发现,只需要考虑两种情况s[i - 1] == t[j - 1]
和s[i - 1] != t[j - 1]
。对于第一种情况,可以用 s[i - 1] 进行匹配,这部分的方法数目是 dp[i - 1][j - 1],也可以不用 s[i - 1] 进行匹配,这部分的数目继承自 dp[i - 1][j];对于第二种情况,没法匹配上 s[i - 1] 和 t[j - 1],只能继承自 dp[i - 1][j]。
初始化:dp[0][j] 意味着空数组转换成字符串 t,没有方案可以实现,初始化为0。dp[i][0] 意味着字符串 s 转换成空数组,只有一种方案,初始化为1。dp[0][0]表示空数组到空数组,初始化为1。
cpp
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
int max_value = INT_MAX;
for(int i = 0; i <= s.size(); i++) {
dp[i][0] = 1;
}
for(int j = 1; j <= t.size(); j++) {
dp[0][j] = 0;
}
for(int i = 1; i <= s.size(); i++) {
for(int j = 1; j <= t.size(); j++) {
if(s[i - 1] == t[j - 1]) {
if(dp[i - 1][j] < max_value - dp[i - 1][j - 1])
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[s.size()][t.size()];
}
};