115. 不同的子序列
给你两个字符串
s
和t
,统计并返回在s
的 子序列 中t
出现的个数,结果需要对 10(9) 7 取模。示例 1:
输入:s = "rabbbit", t = "rabbit"输出:3 解释: 如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 rabbbitrabbbitrabbbit
示例 2:
输入:s = "babgbag", t = "bag" 输出:5 解释: 如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 babgbagbabgbagbabgbagbabgbagbabgbag
提示:
1 <= s.length, t.length <= 1000
s
和t
由英文字母组成
1.
动态规划定义,区间dp.
s中[0,i]区间子序列t[0,j]区间子串出现的个数.
如果i位置字符匹配j位置字符,等价于[0,i-1]区间子序列匹配[0,j-1]子串的个数.
如果i位置字符不匹配j位置字符,等价于[0,i-1]区间子序列匹配[0,j]子串个数.
class Solution {
public:
string s, t; // 定义两个字符串s和t,分别代表源字符串和目标字符串
int n, m; // n和m分别代表s和t的长度
vector<vector<int>> dp; // 定义一个二维动态规划数组dp
const int MOD = 1e9 + 7; // 定义一个常数MOD,用于在计算过程中取模
// 初始化函数,用于在每次计算前重置dp数组和s、t字符串
void solveinit() {
n = s.size(), m = t.size();
dp.clear(), dp.resize(n, vector<int>(m, -1)); // 清除dp数组,并根据s和t的长度重新初始化
}
// 深度优先搜索函数,用于计算动态规划的值
int dfs(int i, int j) {
if (i < j) { // 如果i小于j,说明s的当前索引小于t的当前索引,无法形成t,返回0
if (i >= 0 && j >= 0)
dp[i][j] = 0;
return 0;
}
if (j == -1) { // 如果j为-1,说明t已经遍历完,返回1表示有一个匹配的空子序列
return 1;
}
// 如果dp[i][j]已经被计算过,则直接返回其值
if (dp[i][j] != -1)
return dp[i][j];
// 如果s[i]等于t[j],则有两种情况可以形成t[j]:
// 1. 从s[i-1]和t[j-1]继续形成t[j](即s中去掉当前字符)
// 2. 从s[i-1]和t[j]继续形成t[j](即s中保留当前字符)
if (s[i] == t[j]) {
dp[i][j] = (dfs(i - 1, j - 1) + dfs(i - 1, j)) % MOD; // 计算两种情况的和,并取模
} else {
// 如果s[i]不等于t[j],则只有一种情况可以继续形成t[j],即从s[i-1]和t[j]继续
dp[i][j] = dfs(i - 1, j) % MOD;
}
return dp[i][j] % MOD; // 返回计算结果,并取模
}
// 主函数,用于计算s中形成t的不同方式的数量
int numDistinct(string _s, string _t) {
s = _s; // 将传入的字符串赋值给s和t
t = _t;
solveinit(); // 初始化操作
return dfs(n - 1, m - 1) % MOD; // 从s的末尾和t的末尾开始动态规划,并返回结果
}
};
72. 编辑距离
给你两个单词
word1
和word2
, 请返回将word1
转换成word2
所使用的最少操作数 。你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution" 输出:5 解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')
提示:
0 <= word1.length, word2.length <= 500
word1
和word2
由小写英文字母组成
// #define int long long
// #undef int
// #define endl '\n'
// #define p pair<long long, long long>
class Solution {
public:
string word1, word2; // 定义两个单词 word1 和 word2
int n,m; // 分别记录两个单词的长度
vector<vector<int>> memory; // 定义一个二维vector用于记忆化搜索
// 初始化函数,用于初始化单词长度和记忆数组
void solveinit() {
n = word1.size(), m = word2.size(); // 获取单词长度
memory.clear(); // 清空记忆数组
memory.resize(n, vector<int>(m, -1)); // 初始化记忆数组,全部赋值为-1
}
// dfs搜索函数,返回word1转换成word2所需的最少操作数
int dfs(int i, int j) {
if (i == -1) // 当 word1 遍历完毕
return j + 1; // 返回剩余 word2 的长度,即插入操作数
if (j == -1) // 当 word2 遍历完毕
return i + 1; // 返回剩余 word1 的长度,即删除操作数
if(memory[i][j] != -1) return memory[i][j]; // 如果已经计算过该状态,直接返回记忆数组中的结果
if (word1[i] == word2[j]) // 当两个位置字符相等时,无需操作
return dfs(i - 1, j - 1); // 继续比较下一位
// 分别计算插入、替换、删除三种操作的操作数
int nums1 = dfs(i, j - 1) + 1; // 插入操作
int nums2 = dfs(i - 1, j - 1) + 1; // 替换操作
int nums3 = dfs(i - 1, j) + 1; // 删除操作
memory[i][j] = min(nums1, min(nums2, nums3)); // 记录当前状态下的最小操作数
return memory[i][j]; // 返回最小操作数
}
// 主函数,计算最小操作数
int minDistance(string _word1, string _word2) {
word1 = _word1, word2 = _word2; // 初始化单词
solveinit(); // 执行初始化函数
return dfs(word1.size() - 1, word2.size() - 1); // 返回计算结果
}
};
97. 交错字符串
给定三个字符串
s1
、s2
、s3
,请你帮忙验证s3
是否是由s1
和s2
交错 组成的。两个字符串
s
和t
交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空子字符串
:
s = s(1)s(2)... + s(n)
t = t(1)t(2)... + t(m)
|n - m| <= 1
交错 是
s(1)t(1)s(2)t(2)s(3)t(3)...
或者t(1)s(1)t(2)s(2)t(3)s(3)...
注意:
a + b
意味着字符串a
和b
连接。示例 1:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出:true
示例 2:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc" 输出:false
示例 3:
输入:s1 = "", s2 = "", s3 = "" 输出:true
提示:
0 <= s1.length, s2.length <= 100
0 <= s3.length <= 200
s1
、s2
、和s3
都由小写英文字母组成进阶:您能否仅使用
O(s2.length)
额外的内存空间来解决它?
1.
注意判断的时候不要数组越界了.
边界的处理不要越界.
class Solution {
public:
string s1, s2, s3; // 定义三个字符串s1、s2和s3
int n1, n2, n3; // n1、n2和n3分别代表s1、s2和s3的长度
vector<vector<int>> dp; // 定义一个二维动态规划数组dp,用于存储子问题的解
// 初始化函数,用于在每次计算前重置dp数组和s1、s2、s3字符串
void solveinit() {
n1 = s1.size(), n2 = s2.size(), n3 = s3.size();
dp.clear(), dp.resize(n1, vector<int>(n2, -1)); // 清除dp数组,并根据s1和s2的长度重新初始化
}
// 深度优先搜索函数,用于计算动态规划的值
int dfs(int i, int j) {
// 如果s1和s2都遍历完,检查s3是否也遍历完
if (i == -1 && j == -1)
return n3 == 0 ? true : false;
// 如果s1遍历完,检查剩余的s2是否能与s3的剩余部分匹配
if (i == -1) {
if (s2.substr(0, j + 1) == s3.substr(i + j + 1, j + 1))
return true;
else
return false;
}
// 如果s2遍历完,检查剩余的s1是否能与s3的剩余部分匹配
if (j == -1) {
if (s1.substr(0, i + 1) == s3.substr(j + i + 1, i + 1))
return true;
else
return false;
}
// 如果当前状态已经被计算过,则直接返回其值
if (dp[i][j] != -1)
return dp[i][j];
// 如果s1[i]和s2[j]都能与s3[i+j+1]匹配,则有两种走法:从s1去掉当前字符继续,或从s2去掉当前字符继续
if (i + j + 1 < n3 && s1[i] == s3[i + j + 1] && s2[j] == s3[i + j + 1]) {
dp[i][j] = dfs(i - 1, j) | dfs(i, j - 1); // 两种走法的逻辑或
}
// 如果只有s2[j]能与s3[i+j+1]匹配,则只能从s2去掉当前字符继续
else if (i + j + 1 < n3 && s2[j] == s3[i + j + 1]) {
dp[i][j] = dfs(i, j - 1);
}
// 如果只有s1[i]能与s3[i+j+1]匹配,则只能从s1去掉当前字符继续
else if (i + j + 1 < n3 && s1[i] == s3[i + j + 1]) {
dp[i][j] = dfs(i - 1, j);
} else {
// 如果s1[i]和s2[j]都不能与s3[i+j+1]匹配,则当前状态为false
dp[i][j] = false;
}
return dp[i][j]; // 返回计算结果
}
// 主函数,用于验证s3是否由s1和s2交错组成
bool isInterleave(string _s1, string _s2, string _s3) {
s1 = _s1, s2 = _s2, s3 = _s3; // 将传入的字符串赋值给s1、s2和s3
solveinit(); // 初始化操作
// 如果s1和s2的总长度小于s3的长度,则s3不可能由s1和s2交错组成
if (n1 + n2 < n3)
return false;
// 对s1和s2的所有前缀进行dfs搜索,填充dp数组
for (int i = 0; i < n1; i++) {
for (int j = 0; j < n2; j++) {
dfs(i, j);
}
}
// 从s1和s2的末尾开始验证,是否能形成s3
return dfs(n1 - 1, n2 - 1);
}
};
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!