0097-交错字符串

Raphael Liu Lv10

给定三个字符串 s1s2s3,请你帮忙验证 s3 是否是由 s1s2 __交错 组成的。

两个字符串 st 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

  • 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 意味着字符串 ab 连接。

示例 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
  • s1s2、和 s3 都由小写英文字母组成

进阶: 您能否仅使用 O(s2.length) 额外的内存空间来解决它?

方法一:动态规划

记 $|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$ 的长度。
 Comments
On this page
0097-交错字符串