字符串交织问题(三个字符串版本)
字数 4619 2025-11-27 08:37:15

字符串交织问题(三个字符串版本)

题目描述

给定三个字符串s1、s2和s3,判断s3是否能由s1和s2的交织形成。交织是指在不改变s1和s2中字符相对顺序的情况下,将它们合并成一个字符串。具体来说,如果s3可以由s1和s2的交织形成,那么s3包含s1的所有字符且顺序不变,同时也包含s2的所有字符且顺序不变。

例如:

  • 输入:s1 = "aab", s2 = "abc", s3 = "aaabbc"
  • 输出:true
  • 解释:s3可以看作是由s1的"aab"和s2的"abc"交织而成(一种交织方式为:a, a, b, a, b, c,但注意这里s1和s2的字符必须保持相对顺序。实际上,s3可以分解为:取s1的前两个字符"aa",然后取s2的第一个字符"a",再取s1的最后一个字符"b",最后取s2的剩余"bc",形成"aa" + "a" + "b" + "bc" = "aaabbc",但这样s2的字符顺序被打乱了('a'应该在'b'和'c'之前,但这里'a'被插入到了中间)。正确的交织必须保持每个字符串自身的顺序。让我们重新检查:s1 = "aab" (a1, a2, b3), s2 = "abc" (a1, b2, c3)。s3 = "aaabbc" 可以表示为:取s1的a1, a2 -> "aa"; 取s2的a1 -> "a" (现在有"aaa"); 取s1的b3 -> "b" (现在有"aaab"); 取s2的b2, c3 -> "bc" (得到"aaabbc")。但是,注意在s2中,a1后面应该是b2,但在我们的取法中,在取了s2的a1之后,我们没有立即取s2的b2,而是先取了s1的b3。这破坏了s2的字符顺序吗?实际上没有,因为交织只要求s2的字符在s3中出现的相对顺序与在s2中相同。也就是说,对于s2中的任意两个字符,如果在s2中x在y前面,那么在s3中x也必须在y前面。同样对于s1。在这个例子中,s2的a1在b2和c3之前,在s3中,a1也确实在b2和c3之前。所以这是有效的交织。

更清晰的例子:
s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" -> true
一种交织方式:s1: a a b c c; s2: d b b c a; 交织: a a d b b c b c a c 保持了两者的顺序。

解题过程

我们将使用动态规划来解决这个问题。定义状态和状态转移方程是关键。

  1. 状态定义
    我们定义dp[i][j]为一个布尔值,表示字符串s1的前i个字符和字符串s2的前j个字符能否交织形成字符串s3的前i+j个字符。

    这里,i的范围是0到len(s1),j的范围是0到len(s2)。当i=0且j=0时,表示用s1的前0个字符和s2的前0个字符交织成s3的前0个字符,这总是true(空字符串可以由两个空字符串交织而成)。

  2. 状态转移方程
    考虑如何形成s3的前i+j个字符。最后一个字符可能来自s1的第i个字符,也可能来自s2的第j个字符。

    • 如果最后一个字符来自s1,那么需要满足:
      • s1的第i个字符等于s3的第i+j个字符(即s3[i+j-1] == s1[i-1],因为字符串索引从0开始)。
      • 并且,s1的前i-1个字符和s2的前j个字符能交织形成s3的前i+j-1个字符(即dp[i-1][j]为true)。
    • 如果最后一个字符来自s2,那么需要满足:
      • s2的第j个字符等于s3的第i+j个字符(即s3[i+j-1] == s2[j-1])。
      • 并且,s1的前i个字符和s2的前j-1个字符能交织形成s3的前i+j-1个字符(即dp[i][j-1]为true)。

    因此,状态转移方程为:
    dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || (dp[i][j-1] && s2[j-1] == s3[i+j-1])

    注意边界情况:

    • 当i=0时,表示s1没有提供字符,那么s3的前j个字符必须全部由s2的前j个字符按顺序构成。所以dp[0][j] = (s2的前j个字符等于s3的前j个字符)。这可以写成:dp[0][j] = dp[0][j-1] && (s2[j-1] == s3[j-1]),并且dp[0][0] = true。
    • 类似地,当j=0时,dp[i][0] = dp[i-1][0] && (s1[i-1] == s3[i-1])
  3. 初始化

    • dp[0][0] = true(两个空字符串交织成空字符串)。
    • 第一列(j=0):dp[i][0] = dp[i-1][0] && (s1[i-1] == s3[i-1]),对于i从1到len(s1)。
    • 第一行(i=0):dp[0][j] = dp[0][j-1] && (s2[j-1] == s3[j-1]),对于j从1到len(s2)。
  4. 填表顺序
    我们需要计算dp[i][j]对于所有i和j的值。由于dp[i][j]依赖于dp[i-1][j]和dp[i][j-1],我们可以按行优先(i从0到len(s1),对于每个i,j从0到len(s2))或列优先的顺序填充dp表。

  5. 最终结果
    dp[len(s1)][len(s2)]的值就是问题的答案,即s1的全部字符和s2的全部字符能否交织成s3的全部字符。注意,只有当len(s1) + len(s2) == len(s3)时才有可能,否则直接返回false。

举例说明

以s1 = "aab", s2 = "abc", s3 = "aaabbc"为例(长度3+3=6,符合)。

初始化dp表,大小为(4)x(4)(i=0..3, j=0..3)。

  • dp[0][0] = true。
  • 第一行(i=0):
    • j=1: dp[0][1] = dp[0][0] && (s2[0]=='a' == s3[0]=='a') -> true && true -> true。
    • j=2: dp[0][2] = dp[0][1] && (s2[1]=='b' == s3[1]=='a') -> true && false -> false。
    • j=3: dp[0][3] = dp[0][2] && ... -> false (因为dp[0][2]已经是false,后面都是false)。
  • 第一列(j=0):
    • i=1: dp[1][0] = dp[0][0] && (s1[0]=='a' == s3[0]=='a') -> true。
    • i=2: dp[2][0] = dp[1][0] && (s1[1]=='a' == s3[1]=='a') -> true。
    • i=3: dp[3][0] = dp[2][0] && (s1[2]=='b' == s3[2]=='a') -> true && false -> false。

现在填充其他部分:

  • i=1, j=1:
    • 来自s1: dp[0][1] && (s1[0]=='a' == s3[1]=='a') -> true && true -> true。
    • 来自s2: dp[1][0] && (s2[0]=='a' == s3[1]=='a') -> true && true -> true。
    • 所以dp[1][1] = true || true -> true。
  • i=1, j=2:
    • 来自s1: dp[0][2] && (s1[0]=='a' == s3[2]=='a') -> false && true -> false。
    • 来自s2: dp[1][1] && (s2[1]=='b' == s3[2]=='a') -> true && false -> false。
    • 所以dp[1][2] = false。
  • i=1, j=3:
    • 类似,取决于dp[0][3]和dp[1][2],都是false,所以false。
  • i=2, j=1:
    • 来自s1: dp[1][1] && (s1[1]=='a' == s3[2]=='a') -> true && true -> true。
    • 来自s2: dp[2][0] && (s2[0]=='a' == s3[2]=='a') -> true && true -> true。
    • 所以dp[2][1] = true。
  • i=2, j=2:
    • 来自s1: dp[1][2] && (s1[1]=='a' == s3[3]=='b') -> false && false -> false。
    • 来自s2: dp[2][1] && (s2[1]=='b' == s3[3]=='b') -> true && true -> true。
    • 所以dp[2][2] = true。
  • i=2, j=3:
    • 来自s1: dp[1][3] && (s1[1]=='a' == s3[4]=='b') -> false && false -> false。
    • 来自s2: dp[2][2] && (s2[2]=='c' == s3[4]=='b') -> true && false -> false。
    • 所以dp[2][3] = false。
  • i=3, j=1:
    • 来自s1: dp[2][1] && (s1[2]=='b' == s3[3]=='b') -> true && true -> true。
    • 来自s2: dp[3][0] && (s2[0]=='a' == s3[3]=='b') -> false && false -> false。
    • 所以dp[3][1] = true。
  • i=3, j=2:
    • 来自s1: dp[2][2] && (s1[2]=='b' == s3[4]=='b') -> true && true -> true。
    • 来自s2: dp[3][1] && (s2[1]=='b' == s3[4]=='b') -> true && true -> true。
    • 所以dp[3][2] = true。
  • i=3, j=3:
    • 来自s1: dp[2][3] && (s1[2]=='b' == s3[5]=='c') -> false && false -> false。
    • 来自s2: dp[3][2] && (s2[2]=='c' == s3[5]=='c') -> true && true -> true。
    • 所以dp[3][3] = true。

最终,dp[3][3] = true,所以s1和s2可以交织成s3。

这个动态规划方法的时间复杂度是O(mn),空间复杂度也是O(mn),其中m和n分别是s1和s2的长度。我们可以通过滚动数组优化空间复杂度到O(n)。

字符串交织问题(三个字符串版本) 题目描述 给定三个字符串s1、s2和s3,判断s3是否能由s1和s2的交织形成。交织是指在不改变s1和s2中字符相对顺序的情况下,将它们合并成一个字符串。具体来说,如果s3可以由s1和s2的交织形成,那么s3包含s1的所有字符且顺序不变,同时也包含s2的所有字符且顺序不变。 例如: 输入:s1 = "aab", s2 = "abc", s3 = "aaabbc" 输出:true 解释:s3可以看作是由s1的"aab"和s2的"abc"交织而成(一种交织方式为:a, a, b, a, b, c,但注意这里s1和s2的字符必须保持相对顺序。实际上,s3可以分解为:取s1的前两个字符"aa",然后取s2的第一个字符"a",再取s1的最后一个字符"b",最后取s2的剩余"bc",形成"aa" + "a" + "b" + "bc" = "aaabbc",但这样s2的字符顺序被打乱了('a'应该在'b'和'c'之前,但这里'a'被插入到了中间)。正确的交织必须保持每个字符串自身的顺序。让我们重新检查:s1 = "aab" (a1, a2, b3), s2 = "abc" (a1, b2, c3)。s3 = "aaabbc" 可以表示为:取s1的a1, a2 -> "aa"; 取s2的a1 -> "a" (现在有"aaa"); 取s1的b3 -> "b" (现在有"aaab"); 取s2的b2, c3 -> "bc" (得到"aaabbc")。但是,注意在s2中,a1后面应该是b2,但在我们的取法中,在取了s2的a1之后,我们没有立即取s2的b2,而是先取了s1的b3。这破坏了s2的字符顺序吗?实际上没有,因为交织只要求s2的字符在s3中出现的相对顺序与在s2中相同。也就是说,对于s2中的任意两个字符,如果在s2中x在y前面,那么在s3中x也必须在y前面。同样对于s1。在这个例子中,s2的a1在b2和c3之前,在s3中,a1也确实在b2和c3之前。所以这是有效的交织。 更清晰的例子: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" -> true 一种交织方式:s1: a a b c c; s2: d b b c a; 交织: a a d b b c b c a c 保持了两者的顺序。 解题过程 我们将使用动态规划来解决这个问题。定义状态和状态转移方程是关键。 状态定义 我们定义dp[ i][ j ]为一个布尔值,表示字符串s1的前i个字符和字符串s2的前j个字符能否交织形成字符串s3的前i+j个字符。 这里,i的范围是0到len(s1),j的范围是0到len(s2)。当i=0且j=0时,表示用s1的前0个字符和s2的前0个字符交织成s3的前0个字符,这总是true(空字符串可以由两个空字符串交织而成)。 状态转移方程 考虑如何形成s3的前i+j个字符。最后一个字符可能来自s1的第i个字符,也可能来自s2的第j个字符。 如果最后一个字符来自s1,那么需要满足: s1的第i个字符等于s3的第i+j个字符(即s3[ i+j-1] == s1[ i-1 ],因为字符串索引从0开始)。 并且,s1的前i-1个字符和s2的前j个字符能交织形成s3的前i+j-1个字符(即dp[ i-1][ j ]为true)。 如果最后一个字符来自s2,那么需要满足: s2的第j个字符等于s3的第i+j个字符(即s3[ i+j-1] == s2[ j-1 ])。 并且,s1的前i个字符和s2的前j-1个字符能交织形成s3的前i+j-1个字符(即dp[ i][ j-1 ]为true)。 因此,状态转移方程为: dp[ i][ j] = (dp[ i-1][ j] && s1[ i-1] == s3[ i+j-1]) || (dp[ i][ j-1] && s2[ j-1] == s3[ i+j-1 ]) 注意边界情况: 当i=0时,表示s1没有提供字符,那么s3的前j个字符必须全部由s2的前j个字符按顺序构成。所以dp[ 0][ j] = (s2的前j个字符等于s3的前j个字符)。这可以写成:dp[ 0][ j] = dp[ 0][ j-1] && (s2[ j-1] == s3[ j-1]),并且dp[ 0][ 0 ] = true。 类似地,当j=0时,dp[ i][ 0] = dp[ i-1][ 0] && (s1[ i-1] == s3[ i-1 ]) 初始化 dp[ 0][ 0 ] = true(两个空字符串交织成空字符串)。 第一列(j=0):dp[ i][ 0] = dp[ i-1][ 0] && (s1[ i-1] == s3[ i-1 ]),对于i从1到len(s1)。 第一行(i=0):dp[ 0][ j] = dp[ 0][ j-1] && (s2[ j-1] == s3[ j-1 ]),对于j从1到len(s2)。 填表顺序 我们需要计算dp[ i][ j]对于所有i和j的值。由于dp[ i][ j]依赖于dp[ i-1][ j]和dp[ i][ j-1 ],我们可以按行优先(i从0到len(s1),对于每个i,j从0到len(s2))或列优先的顺序填充dp表。 最终结果 dp[ len(s1)][ len(s2) ]的值就是问题的答案,即s1的全部字符和s2的全部字符能否交织成s3的全部字符。注意,只有当len(s1) + len(s2) == len(s3)时才有可能,否则直接返回false。 举例说明 以s1 = "aab", s2 = "abc", s3 = "aaabbc"为例(长度3+3=6,符合)。 初始化dp表,大小为(4)x(4)(i=0..3, j=0..3)。 dp[ 0][ 0 ] = true。 第一行(i=0): j=1: dp[ 0][ 1] = dp[ 0][ 0] && (s2[ 0]=='a' == s3[ 0 ]=='a') -> true && true -> true。 j=2: dp[ 0][ 2] = dp[ 0][ 1] && (s2[ 1]=='b' == s3[ 1 ]=='a') -> true && false -> false。 j=3: dp[ 0][ 3] = dp[ 0][ 2] && ... -> false (因为dp[ 0][ 2 ]已经是false,后面都是false)。 第一列(j=0): i=1: dp[ 1][ 0] = dp[ 0][ 0] && (s1[ 0]=='a' == s3[ 0 ]=='a') -> true。 i=2: dp[ 2][ 0] = dp[ 1][ 0] && (s1[ 1]=='a' == s3[ 1 ]=='a') -> true。 i=3: dp[ 3][ 0] = dp[ 2][ 0] && (s1[ 2]=='b' == s3[ 2 ]=='a') -> true && false -> false。 现在填充其他部分: i=1, j=1: 来自s1: dp[ 0][ 1] && (s1[ 0]=='a' == s3[ 1 ]=='a') -> true && true -> true。 来自s2: dp[ 1][ 0] && (s2[ 0]=='a' == s3[ 1 ]=='a') -> true && true -> true。 所以dp[ 1][ 1 ] = true || true -> true。 i=1, j=2: 来自s1: dp[ 0][ 2] && (s1[ 0]=='a' == s3[ 2 ]=='a') -> false && true -> false。 来自s2: dp[ 1][ 1] && (s2[ 1]=='b' == s3[ 2 ]=='a') -> true && false -> false。 所以dp[ 1][ 2 ] = false。 i=1, j=3: 类似,取决于dp[ 0][ 3]和dp[ 1][ 2 ],都是false,所以false。 i=2, j=1: 来自s1: dp[ 1][ 1] && (s1[ 1]=='a' == s3[ 2 ]=='a') -> true && true -> true。 来自s2: dp[ 2][ 0] && (s2[ 0]=='a' == s3[ 2 ]=='a') -> true && true -> true。 所以dp[ 2][ 1 ] = true。 i=2, j=2: 来自s1: dp[ 1][ 2] && (s1[ 1]=='a' == s3[ 3 ]=='b') -> false && false -> false。 来自s2: dp[ 2][ 1] && (s2[ 1]=='b' == s3[ 3 ]=='b') -> true && true -> true。 所以dp[ 2][ 2 ] = true。 i=2, j=3: 来自s1: dp[ 1][ 3] && (s1[ 1]=='a' == s3[ 4 ]=='b') -> false && false -> false。 来自s2: dp[ 2][ 2] && (s2[ 2]=='c' == s3[ 4 ]=='b') -> true && false -> false。 所以dp[ 2][ 3 ] = false。 i=3, j=1: 来自s1: dp[ 2][ 1] && (s1[ 2]=='b' == s3[ 3 ]=='b') -> true && true -> true。 来自s2: dp[ 3][ 0] && (s2[ 0]=='a' == s3[ 3 ]=='b') -> false && false -> false。 所以dp[ 3][ 1 ] = true。 i=3, j=2: 来自s1: dp[ 2][ 2] && (s1[ 2]=='b' == s3[ 4 ]=='b') -> true && true -> true。 来自s2: dp[ 3][ 1] && (s2[ 1]=='b' == s3[ 4 ]=='b') -> true && true -> true。 所以dp[ 3][ 2 ] = true。 i=3, j=3: 来自s1: dp[ 2][ 3] && (s1[ 2]=='b' == s3[ 5 ]=='c') -> false && false -> false。 来自s2: dp[ 3][ 2] && (s2[ 2]=='c' == s3[ 5 ]=='c') -> true && true -> true。 所以dp[ 3][ 3 ] = true。 最终,dp[ 3][ 3 ] = true,所以s1和s2可以交织成s3。 这个动态规划方法的时间复杂度是O(m n),空间复杂度也是O(m n),其中m和n分别是s1和s2的长度。我们可以通过滚动数组优化空间复杂度到O(n)。