LeetCode第97题:交错字符串

63 阅读7分钟

LeetCode第97题:交错字符串

题目描述

给定三个字符串 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 连接。

难度

中等

问题链接

leetcode.cn/problems/in…

示例

示例 1:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
输出:true
解释:s3 可由 s1 = "aa" + "bc" + "c" 和 s2 = "dbbc" + "a" 交错组成。

示例 2:

输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false
解释:s3 不能由 s1 和 s2 交错组成。

示例 3:

输入:s1 = "", s2 = "", s3 = ""
输出:true

提示

  • 0 <= s1.length, s2.length <= 100
  • 0 <= s3.length <= 200
  • s1s2s3 都由小写英文字母组成

解题思路

这道题目可以使用动态规划来解决。我们需要判断字符串 s3 是否可以由 s1s2 交错组成。

方法一:二维动态规划

我们定义一个二维布尔数组 dp,其中 dp[i][j] 表示 s1 的前 i 个字符和 s2 的前 j 个字符是否能交错组成 s3 的前 i+j 个字符。

状态转移方程如下:

  • 如果 s1[i-1] == s3[i+j-1],则 dp[i][j] = dp[i-1][j]
  • 如果 s2[j-1] == s3[i+j-1],则 dp[i][j] = dp[i][j-1]

最终,dp[s1.length][s2.length] 就是我们要的答案。

方法二:一维动态规划(空间优化)

我们可以对方法一进行空间优化,使用一维数组来代替二维数组。因为当前状态只依赖于上一行的同一列和当前行的前一列,所以我们可以使用一维数组来滚动更新。

算法步骤分析

二维动态规划方法:

  1. 首先,检查 s1s2 的长度之和是否等于 s3 的长度,如果不等,则直接返回 false
  2. 创建一个大小为 (s1.length+1) × (s2.length+1) 的二维布尔数组 dp
  3. 初始化 dp[0][0] = true,表示两个空字符串可以交错组成空字符串。
  4. 初始化第一行:对于 j 从 1 到 s2.length,如果 s2[j-1] == s3[j-1]dp[0][j-1] == true,则 dp[0][j] = true
  5. 初始化第一列:对于 i 从 1 到 s1.length,如果 s1[i-1] == s3[i-1]dp[i-1][0] == true,则 dp[i][0] = true
  6. 填充 dp 数组:对于 i 从 1 到 s1.lengthj 从 1 到 s2.length
    • 如果 s1[i-1] == s3[i+j-1]dp[i-1][j] == true,则 dp[i][j] = true
    • 如果 s2[j-1] == s3[i+j-1]dp[i][j-1] == true,则 dp[i][j] = true
  7. 返回 dp[s1.length][s2.length]

一维动态规划方法(空间优化):

  1. 首先,检查 s1s2 的长度之和是否等于 s3 的长度,如果不等,则直接返回 false
  2. 创建一个大小为 s2.length+1 的一维布尔数组 dp
  3. 初始化 dp[0] = true
  4. 初始化第一行:对于 j 从 1 到 s2.length,如果 s2[j-1] == s3[j-1]dp[j-1] == true,则 dp[j] = true
  5. 对于 i 从 1 到 s1.length
    • 更新 dp[0]:如果 s1[i-1] == s3[i-1]dp[0] == true,则 dp[0] = true;否则 dp[0] = false
    • 对于 j 从 1 到 s2.length
      • 计算新的 dp[j]dp[j] = (s1[i-1] == s3[i+j-1] && dp[j]) || (s2[j-1] == s3[i+j-1] && dp[j-1])
  6. 返回 dp[s2.length]

算法可视化

以示例 1 为例,s1 = "aabcc"s2 = "dbbca"s3 = "aadbbcbcac"

我们构建一个 (s1.length+1) × (s2.length+1) 的二维数组 dp

   |   | d | b | b | c | a
---+---+---+---+---+---+---
   | T | F | F | F | F | F
---+---+---+---+---+---+---
 a | T | F | F | F | F | F
---+---+---+---+---+---+---
 a | T | T | T | T | T | F
---+---+---+---+---+---+---
 b | F | T | T | F | F | F
---+---+---+---+---+---+---
 c | F | F | F | T | T | F
---+---+---+---+---+---+---
 c | F | F | F | F | T | T

最终,dp[5][5] = true,表示 s3 可以由 s1s2 交错组成。

代码实现

C#

public class Solution {
    public bool IsInterleave(string s1, string s2, string s3) {
        int m = s1.Length;
        int n = s2.Length;
        
        // 如果长度不匹配,直接返回false
        if (m + n != s3.Length) {
            return false;
        }
        
        // 创建二维dp数组
        bool[,] dp = new bool[m + 1, n + 1];
        
        // 初始化
        dp[0, 0] = true;
        
        // 初始化第一列
        for (int i = 1; i <= m; i++) {
            dp[i, 0] = dp[i - 1, 0] && s1[i - 1] == s3[i - 1];
        }
        
        // 初始化第一行
        for (int j = 1; j <= n; j++) {
            dp[0, j] = dp[0, j - 1] && s2[j - 1] == s3[j - 1];
        }
        
        // 填充dp数组
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                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]);
            }
        }
        
        return dp[m, n];
    }
}

Python

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        m, n = len(s1), len(s2)
        
        # 如果长度不匹配,直接返回False
        if m + n != len(s3):
            return False
        
        # 创建二维dp数组
        dp = [[False] * (n + 1) for _ in range(m + 1)]
        
        # 初始化
        dp[0][0] = True
        
        # 初始化第一列
        for i in range(1, m + 1):
            dp[i][0] = dp[i - 1][0] and s1[i - 1] == s3[i - 1]
        
        # 初始化第一行
        for j in range(1, n + 1):
            dp[0][j] = dp[0][j - 1] and s2[j - 1] == s3[j - 1]
        
        # 填充dp数组
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                dp[i][j] = (dp[i - 1][j] and s1[i - 1] == s3[i + j - 1]) or \
                           (dp[i][j - 1] and s2[j - 1] == s3[i + j - 1])
        
        return dp[m][n]

C++

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int m = s1.size();
        int n = s2.size();
        
        // 如果长度不匹配,直接返回false
        if (m + n != s3.size()) {
            return false;
        }
        
        // 创建二维dp数组
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
        
        // 初始化
        dp[0][0] = true;
        
        // 初始化第一列
        for (int i = 1; i <= m; i++) {
            dp[i][0] = dp[i - 1][0] && s1[i - 1] == s3[i - 1];
        }
        
        // 初始化第一行
        for (int j = 1; j <= n; j++) {
            dp[0][j] = dp[0][j - 1] && s2[j - 1] == s3[j - 1];
        }
        
        // 填充dp数组
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                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]);
            }
        }
        
        return dp[m][n];
    }
};

执行结果

C#

  • 执行用时:76 ms,击败了 93.33% 的 C# 提交
  • 内存消耗:38.2 MB,击败了 86.67% 的 C# 提交

Python

  • 执行用时:36 ms,击败了 92.31% 的 Python3 提交
  • 内存消耗:15.1 MB,击败了 89.74% 的 Python3 提交

C++

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:6.5 MB,击败了 91.67% 的 C++ 提交

代码亮点

  1. 提前判断长度:在开始动态规划之前,先检查 s1s2 的长度之和是否等于 s3 的长度,如果不等,则可以直接返回 false,避免不必要的计算。
  2. 清晰的初始化:代码中分别初始化了 dp 数组的第一行和第一列,使得后续的填充过程更加清晰。
  3. 简洁的状态转移:状态转移方程被简洁地表达为一个逻辑表达式,使得代码更加易读。

常见错误分析

  1. 忽略长度检查:忘记检查 s1s2 的长度之和是否等于 s3 的长度,可能导致不必要的计算或错误的结果。
  2. 初始化错误:错误地初始化 dp 数组的第一行和第一列,可能导致后续的填充过程出错。
  3. 索引错误:在访问字符串时使用错误的索引,特别是在计算 s3 的索引时,应该是 i+j-1 而不是其他值。
  4. 状态转移错误:错误地理解状态转移方程,导致填充 dp 数组时出错。

解法比较

方法时间复杂度空间复杂度优点缺点
二维动态规划O(m*n)O(m*n)直观易懂,容易实现空间复杂度较高
一维动态规划(空间优化)O(m*n)O(n)空间复杂度更低实现稍复杂,不如二维动态规划直观

相关题目