LeetCode 面试经典 150_多维动态规划_交错字符串(97_C++_中等)
题目描述:
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
- s = s1 + s2 + ... + sn
- t = t1 + t2 + ... + tm
- |n - m| <= 1
- 交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3+...
- 注意: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 都由小写英文字母组成
题解:
解题思路:
思路一(动态规划):
1、解题思路
① 此题基本大意为:给你三个字符串 s1、s2、s3。把 s1 的字符顺序保持不变 、s2 的字符顺序保持不变 ,然后把它们穿插合并在一起,拼出来是否刚好等于 s3。
② 首先可能会想到双指针 的方法来求解,但是遇到相同字符二选一的时候,选错分支,后面直接翻车 。
③ 此题的解题非常像 LCS(最长公共子序列)
④ 具体解决思路如下:
- s1.size() + s2.size() != s3.size() → 直接 false,长度对不上一定交错不了
- 状态定义:
dp[i][j]:用 s1 的前 i 个字符、s2 的前 j 个字符,能不能拼成 s3 的前 i+j 个字符i:s1 取了 i 个(s1 0~s1 i-1)j:s2 取了 j 个(s2 0~s2 j-1)- 此时 s3 用到的位置下标:
p = i + j - 1
dp[0][0] = true:s1 取 0 个、s2 取 0 个,空串可以拼成空串(其余为false)- 只拿 s1、不取 s2:
dp[i][0] = dp[i-1][0] && s1[i-1]==s3[i-1] - 只拿 s2、不取 s1:
do[0][j] = dp[0][j-1] && s2[j-1]==s3[j-1] - 最后一个字符来自 s1 或 s2
- dpij = (dpi-1j && s1i-1==s3i+j-1) || (dpij-1 && s2j-1==s3i+j-1);
dp[n][m],n 是 s1 总长,m 是 s2 总长
二维 dp 数组中,当前位置可以由左侧位置 (dpij-·) 和 上侧位置 (dpi-1j) 决定的时候可以考虑使用一维 dp 数组来优化 。
2、复杂度分析:
① 时间复杂度:O(n × m),遍历了一个二维dp 数组
② 空间复杂度:O(n × m),使用了二维dp数组
代码实现
代码实现(思路一(动态规划:二维dp)):
cpp
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
// n:s1长度,m:s2长度,t:s3长度
int n = s1.size(), m = s2.size(), t = s3.size();
// 剪枝:s1和s2字符总数不等于s3,一定无法交错组成
if (n + m != t) return false;
/*
* dp[i][j] 含义:
* 使用 s1 的前 i 个字符、s2 的前 j 个字符,能否匹配 s3 的前 i+j 个字符
*/
vector<vector<bool>> dp(n + 1, vector<bool>(m + 1, false));
// 0个s1 + 0个s2,可以拼成空串s3,初始为true
dp[0][0] = true;
// 初始化:只拿s1,不取s2的情况
for (int i = 1; i <= n; ++i) {
// 前面i-1个字符合法,并且当前s1字符和s3对应位置相等
dp[i][0] = dp[i - 1][0] && (s1[i - 1] == s3[i - 1]);
}
// 初始化:只拿s2,不取s1的情况
for (int j = 1; j <= m; ++j) {
// 前面j-1个字符合法,并且当前s2字符和s3对应位置相等
dp[0][j] = dp[0][j - 1] && (s2[j - 1] == s3[j - 1]);
}
// 双重循环:同时使用s1和s2的字符
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
// s3当前对比的下标:一共取了i+j个字符,下标从0开始,所以-1
int p = i + j - 1;
// 两种可能:
// 1. 最后一个字符来自s1:dp[i-1][j]合法,s1当前字符等于s3[p]
// 2. 最后一个字符来自s2:dp[i][j-1]合法,s2当前字符等于s3[p]
dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[p]) || (dp[i][j - 1] && s2[j - 1] == s3[p]);
}
}
// 取完s1全部n个、s2全部m个字符,是否能拼成s3
return dp[n][m];
}
};
代码实现(思路二(动态规划:一维dp)):
cpp
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n = s1.size(), m = s2.size(), t = s3.size();
// 剪枝:总长度不相等直接返回false
if (n + m != t) return false;
/*
* 一维dp[j]含义:
* 当前已经用了 s1 的前 i 个字符,s2 的前 j 个字符,能否匹配 s3 的前 i+j 个字符
* 因为二维dp[i][j]只依赖dp[i-1][j](上一轮旧值)和dp[i][j-1](本轮刚更新的值)
* 所以可以压缩成一维数组,滚动更新
*/
vector<bool> dp(m + 1, false);
// i=0,j=0:不取s1不取s2,空串匹配空串
dp[0] = true;
// 初始化i=0,只取s2的情况
for (int j = 1; j <= m; ++j) {
dp[j] = dp[j - 1] && (s2[j - 1] == s3[j - 1]);
}
// 遍历s1,i代表取s1前i个字符
for (int i = 1; i <= n; ++i) {
// dp[0]:j=0,不取s2,只取s1前i个字符
dp[0] = dp[0] && (s1[i - 1] == s3[i - 1]);
// 遍历s2,j代表取s2前j个字符
for (int j = 1; j <= m; ++j) {
int p = i + j - 1; // s3当前对比下标
// 两种来源:
// 1. 上一轮的dp[j]:也就是原来的dp[i-1][j],最后一个字符来自s1
// 2. 本轮刚更新的dp[j-1]:也就是原来的dp[i][j-1],最后一个字符来自s2
dp[j] = (dp[j] && s1[i - 1] == s3[p]) || (dp[j - 1] && s2[j - 1] == s3[p]);
}
}
return dp[m];
}
};
LeetCode 面试经典 150_多维动态规划_交错字符串(97_C++)原题链接
欢迎大家和我沟通交流(✿◠‿◠)