注:
题目:
给定三个字符串 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 都由小写英文字母组成
题解:
方法一 动态规划
思路与算法
解决这个问题的正确方法是动态规划。 首先如果 |s1| + |s2| !=|s3∣,那 s3 必然不可能由 s1 和 s2 交错组成。在 |s1| + |s2| = |s3| 时,我们可以用动态规划来求解。
我们定义 f(i, j) 表示 s1的前 i 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i+j 个元素。
如果 s1 的第 i 个元素和 s3 的第 i + j 个元素相等,那么 s1 的前 i 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i+j 个元素取决于 s1 的前 i−1 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i + j - 1 个元素,即此时 f(i,j) 取决于 f(i−1,j),在此情况下如果 f(i - 1, j) 为真,则 f(i,j) 也为真。
同样的,如果 s2 的第 j 个元素和 s3 的第 i+j 个元素相等并且 f(i, j - 1)f(i,j−1) 为真,则 f(i, j)f(i,j) 也为真。
于是我们可以推导出这样的动态规划转移方程:
f(i, j) = [f(i - 1, j) &&s1(i - 1) = s3(i+j-1)] || [f(i, j - 1) && s2(j - 1) = s3(i+j-1)]
复杂度分析
时间复杂度:O(nm),两重循环的时间代价为 O(nm)。
空间复杂度:O(nm)。
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n=s1.size();
int m=s2.size();
int t=s3.size();
if(n+m!=t){
return false;
}
vector<vector<int>> dp(n+1,vector<int>(m+1));
dp[0][0]=1;
// 第0行:s1前缀长度0 + s2前缀长度i,能否交错组成s3前缀长度i
for(int j=1;j<=m;j++){
dp[0][j]=(dp[0][j-1]&&s2[j-1]==s3[0+j-1]);
}
// 第0列:s1前缀长度i + s2前缀长度0,能否交错组成s3前缀长度i
for(int i=1;i<=n;i++){
dp[i][0]=(dp[i-1][0]&&s1[i-1]==s3[i+0-1]);
}
// 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
// 考虑s3的最后一个字符s3[i+j-1]来自哪里?做状态转移:
// 如果来自s1[i-1],则dp[i][j] 为:s1前缀长度i-1 + s2前缀长度j 能否交错组成s3前缀长度i+j-1,即:dp[i-1][j]
// 如果来自s2[j-1],则dp[i][j] 为:s1前缀长度i + s2前缀长度j-1 能否交错组成s3前缀长度i+j-1,即:dp[i][j-1]
dp[i][j]=(s1[i-1]==s3[i+j-1]&&dp[i-1][j])||(s2[j-1]==s3[i+j-1]&&dp[i][j-1]);
}
}
return dp[n][m];
}
};
在不单独考虑第一行以及第一列的情况下,以上写法可以合并为
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n=s1.size();
int m=s2.size();
int t=s3.size();
if(n+m!=t){
return false;
}
vector<vector<int>> dp(n+1,vector<int>(m+1));
dp[0][0]=1;
// 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
//注意第二个式子用了或操作,如果不加上这个或操作,假设dp的值通过s1计算完成之后变成了True,但是通过s2之后又变成了false,对于后面的运算会产生影响
if(i>0){
dp[i][j]=(s1[i-1]==s3[i+j-1]&&dp[i-1][j]);
}
if(j>0){
dp[i][j]|=(s2[j-1]==s3[i+j-1]&&dp[i][j-1]);
}
}
}
return dp[n][m];
}
};
方法二 优化
由于 第 i 行只和第i−1 行相关,虽然有两层for循环,但是内层循环每次遍历都只用一维数组dp,dp 存放的就是第 i-1 行的值,所以可以直接将 i 所在维度去掉。
dp[i][j] = (s1[i - 1] == s3[i+j-1] && dp[i - 1][j])
变为
dp[j] = (s1[i - 1] == s3[i+j-1]&&dp[j])
dp[i][j] |= (s2[j - 1] == s3[i+j-1] && dp[i][j - 1])
变为
dp[j]|=(s2[j-1]==s3[i+j-1]&&dp[j-1])
复杂度分析
时间复杂度:O(nm),两重循环的时间代价为O(nm)。
空间复杂度:O(m),即 s2的长度。
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n=s1.size();
int m=s2.size();
int t=s3.size();
if(n+m!=t){
return false;
}
//dp用来表示每行的状态
vector<int> dp(m+1);
dp[0]=1;
// 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i>0){
dp[j] =(s1[i-1]==s3[i+j-1]&&dp[j]);
}
if(j>0){
dp[j]|=(s2[j-1]==s3[i+j-1]&&dp[j-1]);
}
}
}
return dp[m];
}
};