给定三个字符串 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
都由小写英文字母组成
注意:本题与主站 97 题相同: https://leetcode-cn.com/problems/interleaving-string/
方法一:动态规划 记 |s_1| = n,|s_2| = m。
思路与算法
双指针法错在哪里? 也许有同学看到这道题目的第一反应是使用双指针法解决这个问题,指针 p_1 一开始指向 s_1 的头部,指针 p_2 一开始指向 s_2 的头部,指针 p_3 指向 s_3 的头部,每次观察 p_1 和 p_2 指向的元素哪一个和 p_3 指向的元素相等,相等则匹配并后移指针。样例就是一个很好的反例,用这种方法判断 s_1 = {\rm aabcc,s_2 = {\rm dbbca,s_3 = {\rm aadbbcbcac 时,得到的结果是 \rm False,实际应该是 \rm True。
解决这个问题的正确方法是动态规划。 首先如果 |s_1| + |s_2| \neq |s_3|,那 s_3 必然不可能由 s_1 和 s_2 交织组成。在 |s_1| + |s_2| = |s_3| 时,我们可以用动态规划来求解。我们定义 f(i, j) 表示 s_1 的前 i 个元素和 s_2 的前 j 个元素是否能交织组成 s_3 的前 i + j 个元素。如果 s_1 的第 i 个元素和 s_3 的第 i + j 个元素相等,那么 s_1 的前 i 个元素和 s_2 的前 j 个元素是否能交织组成 s_3 的前 i + j 个元素取决于 s_1 的前 i - 1 个元素和 s_2 的前 j 个元素是否能交织组成 s_3 的前 i + j - 1 个元素,即此时 f(i, j) 取决于 f(i - 1, j),在此情况下如果 f(i - 1, j) 为真,则 f(i, j) 也为真。同样的,如果 s_2 的第 j 个元素和 s_3 的第 i + j 个元素相等并且 f(i, j - 1) 为真,则 f(i, j) 也为真。于是我们可以推导出这样的动态规划转移方程:
f(i, j) = [f(i - 1, j) , {\rm and} , s_1(i - 1) = s_3(p)] , {\rm or} , [f(i, j - 1) , {\rm and} , s_2(j - 1) = s_3(p)]
其中 p = i + j - 1。边界条件为 f(0, 0) = {\rm True。至此,我们很容易可以给出这样一个实现:
[sol0-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : bool isInterleave (string s1, string s2, string s3) { auto f = vector < vector <int > > (s1.size () + 1 , vector <int > (s2.size () + 1 , false )); int n = s1.size (), m = s2.size (), t = s3.size (); if (n + m != t) { return false ; } f[0 ][0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[i][j] |= (f[i - 1 ][j] && s1[i - 1 ] == s3[p]); } if (j > 0 ) { f[i][j] |= (f[i][j - 1 ] && s2[j - 1 ] == s3[p]); } } } return f[n][m]; } };
[sol0-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public boolean isInterleave (String s1, String s2, String s3) { int n = s1.length(), m = s2.length(), t = s3.length(); if (n + m != t) { return false ; } boolean [][] f = new boolean [n + 1 ][m + 1 ]; f[0 ][0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[i][j] = f[i][j] || (f[i - 1 ][j] && s1.charAt(i - 1 ) == s3.charAt(p)); } if (j > 0 ) { f[i][j] = f[i][j] || (f[i][j - 1 ] && s2.charAt(j - 1 ) == s3.charAt(p)); } } } return f[n][m]; } }
[sol0-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func isInterleave (s1 string , s2 string , s3 string ) bool { n, m, t := len (s1), len (s2), len (s3) if (n + m) != t { return false } f := make ([][]bool , n + 1 ) for i := 0 ; i <= n; i++ { f[i] = make ([]bool , m + 1 ) } f[0 ][0 ] = true for i := 0 ; i <= n; i++ { for j := 0 ; j <= m; j++ { p := i + j - 1 if i > 0 { f[i][j] = f[i][j] || (f[i-1 ][j] && s1[i-1 ] == s3[p]) } if j > 0 { f[i][j] = f[i][j] || (f[i][j-1 ] && s2[j-1 ] == s3[p]) } } } return f[n][m] }
[sol0-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 bool isInterleave (char * s1, char * s2, char * s3) { int n = strlen (s1), m = strlen (s2), t = strlen (s3); int f[n + 1 ][m + 1 ]; memset (f, 0 , sizeof (f)); if (n + m != t) { return false ; } f[0 ][0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[i][j] |= (f[i - 1 ][j] && s1[i - 1 ] == s3[p]); } if (j > 0 ) { f[i][j] |= (f[i][j - 1 ] && s2[j - 1 ] == s3[p]); } } } return f[n][m]; }
不难看出这个实现的时间复杂度和空间复杂度都是 O(nm)。
使用滚动数组优化空间复杂度。 因为这里数组 f 的第 i 行只和第 i - 1 行相关,所以我们可以用滚动数组优化这个动态规划,这样空间复杂度可以变成 O(m)。敲黑板:我们又遇到「滚动数组」优化啦!不会的同学一定要学习哟。如果还没有做过这几个题建议大家做一下,都可以使用这个思想进行优化:
下面给出滚动数组优化的代码。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : bool isInterleave (string s1, string s2, string s3) { auto f = vector <int > (s2.size () + 1 , false ); int n = s1.size (), m = s2.size (), t = s3.size (); if (n + m != t) { return false ; } f[0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[j] &= (s1[i - 1 ] == s3[p]); } if (j > 0 ) { f[j] |= (f[j - 1 ] && s2[j - 1 ] == s3[p]); } } } return f[m]; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public boolean isInterleave (String s1, String s2, String s3) { int n = s1.length(), m = s2.length(), t = s3.length(); if (n + m != t) { return false ; } boolean [] f = new boolean [m + 1 ]; f[0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[j] = f[j] && s1.charAt(i - 1 ) == s3.charAt(p); } if (j > 0 ) { f[j] = f[j] || (f[j - 1 ] && s2.charAt(j - 1 ) == s3.charAt(p)); } } } return f[m]; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func isInterleave (s1 string , s2 string , s3 string ) bool { n, m, t := len (s1), len (s2), len (s3) if (n + m) != t { return false } f := make ([]bool , m + 1 ) f[0 ] = true for i := 0 ; i <= n; i++ { for j := 0 ; j <= m; j++ { p := i + j - 1 if i > 0 { f[j] = f[j] && s1[i-1 ] == s3[p] } if j > 0 { f[j] = f[j] || f[j-1 ] && s2[j-1 ] == s3[p] } } } return f[m] }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 bool isInterleave (char * s1, char * s2, char * s3) { int n = strlen (s1), m = strlen (s2), t = strlen (s3); int f[m + 1 ]; memset (f, 0 , sizeof (f)); if (n + m != t) { return false ; } f[0 ] = true ; for (int i = 0 ; i <= n; ++i) { for (int j = 0 ; j <= m; ++j) { int p = i + j - 1 ; if (i > 0 ) { f[j] &= (s1[i - 1 ] == s3[p]); } if (j > 0 ) { f[j] |= (f[j - 1 ] && s2[j - 1 ] == s3[p]); } } } return f[m]; }
复杂度分析
时间复杂度:O(nm),两重循环的时间代价为 O(nm)。
空间复杂度:O(m),即 s_2 的长度。