线性动态规划:交错字符串
字数 2746 2025-12-22 07:43:04

好的,我们开始一个新题目。

线性动态规划:交错字符串

题目描述

给定三个字符串 s1, s2s3。你需要判断 s3 是否可以由 s1s2 交错 组成。交错的定义是:保持 s1s2 中每个字符的原有相对顺序,将它们混合在一起。

示例 1:

输入: s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac”
输出: true
解释: 交错方式是:s1中的 “a” + s1中的 “a” + s2中的 “d” + s1中的 “b” + s2中的 “b” + s1中的 “c” + s2中的 “c” + s1中的 “c”。
      即:aa d b b c c a c (用空格分隔只是为了显示s1和s2的来源,实际字符串是连续的)

示例 2:

输入: s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbbaccc”
输出: false

解题过程循序渐进讲解

这是一个经典的二维线性动态规划问题。我们的目标是判断能否用 s1s2 “编织” 出 s3,关键在于保持每个原字符串内部的字符顺序。

第一步:定义状态

我们定义一个二维布尔数组 dp[i][j],其含义如下:

  • i 表示从 s1 中已经使用了前 i 个字符(索引从 1 开始计数,i 的范围是 0len(s1))。
  • j 表示从 s2 中已经使用了前 j 个字符。
  • dp[i][j] 表示:能否由 s1 的前 i 个字符和 s2 的前 j 个字符,交错组成 s3 的前 (i+j) 个字符

dp[0][0] 表示两个空字符串能否交错组成空字符串,显然是 true

第二步:状态转移方程

我们如何计算 dp[i][j] 呢?s3 的前 (i+j) 个字符的最后一个字符,要么来自 s1 的第 i 个字符,要么来自 s2 的第 j 个字符(如果 ij 为 0,则只能来自其中一个)。

  1. 来自 s1

    • 前提条件:i > 0(s1还有字符可用)。
    • 逻辑判断:s1[i-1](因为字符串索引从0开始)必须等于 s3[i+j-1](s3的前(i+j)个字符的最后一个)。
    • 如果这个字符来自s1,那么dp[i][j] 的真假就取决于:能否由 s1 的前 (i-1) 个字符和 s2 的前 j 个字符组成 s3 的前 (i+j-1) 个字符,也就是 dp[i-1][j]
  2. 来自 s2

    • 前提条件:j > 0
    • 逻辑判断:s2[j-1] 必须等于 s3[i+j-1]
    • 如果这个字符来自s2,那么dp[i][j] 的真假就取决于:能否由 s1 的前 i 个字符和 s2 的前 (j-1) 个字符组成 s3 的前 (i+j-1) 个字符,也就是 dp[i][j-1]

dp[i][j] 为真的条件是:来自s1或来自s2的两种情况,只要有一种成立即可

因此,状态转移方程为:

如果 i > 0 且 s1[i-1] == s3[i+j-1]: 情况1 = dp[i-1][j]
如果 j > 0 且 s2[j-1] == s3[i+j-1]: 情况2 = dp[i][j-1]
dp[i][j] = 情况1 OR 情况2

(当 ij 都为0时,是一种特殊情况,初始化为 true

第三步:初始化

我们需要初始化第一行和第一列,这代表了只用其中一个字符串去匹配 s3 的前缀。

  • dp[0][j]:表示只用 s2 的前 j 个字符去匹配 s3 的前 j 个字符。只有当 s2 的前 j 个字符完全等于 s3 的前 j 个字符时,才为 true
    • dp[0][0] = true(两个空字符串匹配空字符串)。
    • 对于 j >= 1dp[0][j] = dp[0][j-1] && (s2[j-1] == s3[j-1])。意思是,前 j-1 个字符已经能匹配上,并且第 j 个字符也相等。
  • dp[i][0]:表示只用 s1 的前 i 个字符去匹配 s3 的前 i 个字符。逻辑类似:
    • dp[i][0] = dp[i-1][0] && (s1[i-1] == s3[i-1])

第四步:计算顺序

我们的状态 dp[i][j] 依赖于它左边 dp[i][j-1] 和上方 dp[i-1][j] 的值。
因此,计算顺序应该是:从左到右,从上到下地遍历这个二维表格。外层循环遍历 i(0到len(s1)),内层循环遍历 j(0到len(s2))。

第五步:最终结果

我们最终想知道,能否用 s1 的全部(len(s1) 个字符)和 s2 的全部(len(s2) 个字符)交错组成 s3 的全部(len(s3) = len(s1)+len(s2) 个字符)。
所以,答案就是 dp[len(s1)][len(s2)]


让我们通过一个简单的例子来走一遍流程

s1 = “a”, s2 = “b”, s3 = “ab”
长度:len1 = 1, len2 = 1, len3 = 2

  1. 初始化 dp 表,大小为 (len1+1) x (len2+1) = 2 x 2

    • dp[0][0] = true
    • 第一行 dp[0][1]:只用s2的前1个字符”b”去匹配s3的前1个字符”a”,’b’ != ‘a’,所以 dp[0][1] = false
    • 第一列 dp[1][0]:只用s1的前1个字符”a”去匹配s3的前1个字符”a”,’a’ == ‘a’,且 dp[0][0] = true,所以 dp[1][0] = true

    初始 dp 表:

        “”  “b”
    “”  T    F
    “a” T    ?
    
  2. 计算 dp[1][1]

    • i=1, j=1s3[i+j-1] = s3[1] = ‘b’
    • 情况1(来自s1):i>0成立,s1[0] = ‘a’ 不等于 ’b’,此路不通。
    • 情况2(来自s2):j>0成立,s2[0] = ‘b’ 等于 ’b’,需要看 dp[1][0],其值为 true。所以此路通。
    • dp[1][1] = true OR false = true

    最终 dp 表:

        “”  “b”
    “”  T    F
    “a” T    T
    
  3. 结果:dp[1][1] = true,所以 s3 = “ab” 可以由 s1 = “a”s2 = “b” 交错组成。


复杂度分析

  • 时间复杂度:O(m * n),其中 m 和 n 分别是 s1 和 s2 的长度。我们需要填充一个 (m+1) x (n+1) 的 DP 表。
  • 空间复杂度:O(m * n),即 DP 表所占用的空间。可以优化到 O(min(m, n)),即使用一维数组进行滚动更新。

核心思想总结
这个问题的核心在于设计 dp[i][j] 这个状态,它精准地描述了“前 i 个字符和前 j 个字符能交错组成前 (i+j) 个字符”这个子问题。状态转移则非常直观地模拟了交错的过程:最后一个字符要么来自 s1,要么来自 s2,只要有一种来源能匹配上 s3 的对应字符,并且剩下的部分也满足交错条件即可。通过从空串开始逐步增加字符,我们最终判断出整个字符串是否满足条件。

好的,我们开始一个新题目。 线性动态规划:交错字符串 题目描述 给定三个字符串 s1 , s2 和 s3 。你需要判断 s3 是否可以由 s1 和 s2 交错 组成。交错的定义是:保持 s1 和 s2 中每个字符的原有相对顺序,将它们混合在一起。 示例 1: 示例 2: 解题过程循序渐进讲解 这是一个经典的二维线性动态规划问题。我们的目标是判断能否用 s1 和 s2 “编织” 出 s3 ,关键在于保持每个原字符串内部的字符顺序。 第一步:定义状态 我们定义一个二维布尔数组 dp[i][j] ,其含义如下: i 表示从 s1 中已经使用了前 i 个字符(索引从 1 开始计数, i 的范围是 0 到 len(s1) )。 j 表示从 s2 中已经使用了前 j 个字符。 dp[i][j] 表示: 能否由 s1 的前 i 个字符和 s2 的前 j 个字符,交错组成 s3 的前 (i+j) 个字符 。 dp[0][0] 表示两个空字符串能否交错组成空字符串,显然是 true 。 第二步:状态转移方程 我们如何计算 dp[i][j] 呢? s3 的前 (i+j) 个字符的最后一个字符,要么来自 s1 的第 i 个字符,要么来自 s2 的第 j 个字符(如果 i 或 j 为 0,则只能来自其中一个)。 来自 s1 : 前提条件: i > 0 (s1还有字符可用)。 逻辑判断: s1[i-1] (因为字符串索引从0开始)必须等于 s3[i+j-1] (s3的前 (i+j) 个字符的最后一个)。 如果这个字符来自s1,那么 dp[i][j] 的真假就取决于:能否由 s1 的前 (i-1) 个字符和 s2 的前 j 个字符组成 s3 的前 (i+j-1) 个字符,也就是 dp[i-1][j] 。 来自 s2 : 前提条件: j > 0 。 逻辑判断: s2[j-1] 必须等于 s3[i+j-1] 。 如果这个字符来自s2,那么 dp[i][j] 的真假就取决于:能否由 s1 的前 i 个字符和 s2 的前 (j-1) 个字符组成 s3 的前 (i+j-1) 个字符,也就是 dp[i][j-1] 。 dp[i][j] 为真的条件是: 来自s1或来自s2的两种情况,只要有一种成立即可 。 因此,状态转移方程为: (当 i 和 j 都为0时,是一种特殊情况,初始化为 true ) 第三步:初始化 我们需要初始化第一行和第一列,这代表了只用其中一个字符串去匹配 s3 的前缀。 dp[0][j] :表示只用 s2 的前 j 个字符去匹配 s3 的前 j 个字符。只有当 s2 的前 j 个字符 完全等于 s3 的前 j 个字符时,才为 true 。 dp[0][0] = true (两个空字符串匹配空字符串)。 对于 j >= 1 : dp[0][j] = dp[0][j-1] && (s2[j-1] == s3[j-1]) 。意思是,前 j-1 个字符已经能匹配上,并且第 j 个字符也相等。 dp[i][0] :表示只用 s1 的前 i 个字符去匹配 s3 的前 i 个字符。逻辑类似: dp[i][0] = dp[i-1][0] && (s1[i-1] == s3[i-1]) 第四步:计算顺序 我们的状态 dp[i][j] 依赖于它左边 dp[i][j-1] 和上方 dp[i-1][j] 的值。 因此,计算顺序应该是: 从左到右,从上到下 地遍历这个二维表格。外层循环遍历 i (0到 len(s1) ),内层循环遍历 j (0到 len(s2) )。 第五步:最终结果 我们最终想知道,能否用 s1 的全部( len(s1) 个字符)和 s2 的全部( len(s2) 个字符)交错组成 s3 的全部( len(s3) = len(s1)+len(s2) 个字符)。 所以,答案就是 dp[len(s1)][len(s2)] 。 让我们通过一个简单的例子来走一遍流程 s1 = “a”, s2 = “b”, s3 = “ab” 长度: len1 = 1, len2 = 1, len3 = 2 初始化 dp 表,大小为 (len1+1) x (len2+1) = 2 x 2 。 dp[0][0] = true 第一行 dp[0][1] :只用 s2 的前1个字符”b”去匹配 s3 的前1个字符”a”, ’b’ != ‘a’ ,所以 dp[0][1] = false 。 第一列 dp[1][0] :只用 s1 的前1个字符”a”去匹配 s3 的前1个字符”a”, ’a’ == ‘a’ ,且 dp[0][0] = true ,所以 dp[1][0] = true 。 初始 dp 表: 计算 dp[1][1] : i=1, j=1 。 s3[i+j-1] = s3[1] = ‘b’ 情况1(来自s1): i>0 成立, s1[0] = ‘a’ 不等于 ’b’ ,此路不通。 情况2(来自s2): j>0 成立, s2[0] = ‘b’ 等于 ’b’ ,需要看 dp[1][0] ,其值为 true 。所以此路通。 dp[1][1] = true OR false = true 最终 dp 表: 结果: dp[1][1] = true ,所以 s3 = “ab” 可以由 s1 = “a” 和 s2 = “b” 交错组成。 复杂度分析 时间复杂度:O(m * n),其中 m 和 n 分别是 s1 和 s2 的长度。我们需要填充一个 (m+1) x (n+1) 的 DP 表。 空间复杂度:O(m * n),即 DP 表所占用的空间。可以优化到 O(min(m, n)),即使用一维数组进行滚动更新。 核心思想总结 这个问题的核心在于设计 dp[i][j] 这个状态,它精准地描述了“前 i 个字符和前 j 个字符能交错组成前 (i+j) 个字符”这个子问题。状态转移则非常直观地模拟了交错的过程:最后一个字符要么来自 s1,要么来自 s2,只要有一种来源能匹配上 s3 的对应字符,并且剩下的部分也满足交错条件即可。通过从空串开始逐步增加字符,我们最终判断出整个字符串是否满足条件。