数据机构与算法之深入解析“交错字符串”的求解思路与算法示例

Posted Serendipity·y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据机构与算法之深入解析“交错字符串”的求解思路与算法示例相关的知识,希望对你有一定的参考价值。

一、题目要求

  • 给定三个字符串 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 都由小写英文字母组成。

二、求解算法

① 动态规划

  • 使用 dp[i][j] 表示 S1 的前 i 个字符和 S2 的前 j 个字符是否能构成 S3 的前 i+j 个字符,首先 dp[0][0] 一定是 True:

  • 初始化 S1、S2、S3 的长度分别为 len1,len2,len3;
  • 若 len1+len2!=len3,表示一定不能构成交错字符串,返回 False;
  • 初始化 dp 为 (len1+1)∗(len2+1) 的 False 数组。
  • 初始化第一列 dp[i][0],遍历第一列,遍历区间 [1,len1+1):
    • dp[i][0]=dp[i−1][0]ands1[i−1]==s3[i−1],表示 S1 的前 i 位是否能构成 S3 的前 i 位。
    • 因此需要满足的条件为,前 i−1 位可以构成 S3 的前 i−1 位且 S1 的第 i 位(s1[i−1])等于 S3 的第 i 位(s3[i−1])。
  • 初始化第一行 dp[0][j],遍历第一行,遍历区间 [1,len2+1):
    • dp[0][i]=dp[0][i−1]ands2[i−1]==s3[i−1],表示 S2 的前 i 位是否能构成 S3 的前 i 位。
    • 因此需要满足的条件为,前 i−1 位可以构成 S3 的前 i−1 位且 S2 的第 i 位(s2[i−1])等于 S3 的第 i 位(s3[i−1])
  • 遍历 dp 数组,每一行 i,遍历区间 [1,len1+1):每一列 j,遍历区间 [1,len2+1):
    • dp[i][j]=(dp[i][j-1]\\ and\\ s2[j-1]==s3[i+j-1])\\ or\\ (dp[i-1][j]\\ and\\ s1[i-1]==s3[i+j-1])$$ 。解释: s 1 s1 s1 i i i 位和 s 2 s2 s2 的前 j j j 位能否组成 s 3 s3 s3 的前 i + j i+j i+j 位取决于两种情况:
    • S1 的前 i 个字符和 S2 的前 j−1 个字符能否构成 S3 的前 i+j−1 位,且 S2 的第 j 位(s2[j−1])是否等于 S3 的第 i+j 位(s3[i+j−1])。
  • 返回 dp[−1][−1]。
  • Python 示例:
class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        len1=len(s1)
        len2=len(s2)
        len3=len(s3)
        if(len1+len2!=len3):
            return False
        dp=[[False]*(len2+1) for i in range(len1+1)]
        dp[0][0]=True
        for i in range(1,len1+1):
            dp[i][0]=(dp[i-1][0] and s1[i-1]==s3[i-1])
        for i in range(1,len2+1):
            dp[0][i]=(dp[0][i-1] and s2[i-1]==s3[i-1])
        for i in range(1,len1+1):
            for j in range(1,len2+1):
                dp[i][j]=(dp[i][j-1] and s2[j-1]==s3[i+j-1]) or (dp[i-1][j] and s1[i-1]==s3[i+j-1])
        return dp[-1][-1]

② 动态规划(LeetCode 官方解法)

  • 记 ∣s1∣=n,∣s2∣=m。
  • 双指针法错在哪里? 也许我们第一反应是使用双指针法解决这个问题,指针 p1 一开始指向 s1 的头部,指针 p2一开始指向 s2 的头部,指针 p3 指向 s3 的头部,每次观察 p1 和 p2 指向的元素哪一个和 p3 指向的元素相等,相等则匹配并后移指针。样例就是一个很好的反例,用这种方法判断 s1 = aabcc,s2 = dbbca,s3 = aadbbcbcac 时,得到的结果是 False,实际应该是 True。
  • 解决这个问题的正确方法是动态规划:
    • 首先如果 ∣s1∣ + ∣s2∣ = ∣s3∣,那 s3 必然不可能由 s1 和 s2 交错组成。在 ∣s1∣ + ∣s2∣ = ∣s3∣ 时,可以用动态规划来求解。
    • 定义 f(i,j) 表示 s1 的前 i 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i+j 个元素;
    • 如果 s1 的第 i 个元素和 s3 的第 i+j 个元素相等,那么 s1 的前 i 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i+j 个元素取决于 s1 的前 i−1 个元素和 s2 的前 j 个元素是否能交错组成 s3 的前 i+j−1 个元素,即此时 f(i,j) 取决于 f(i−1,j),在此情况下如果 f(i−1,j) 为真,则 f(i,j) 也为真。
    • 同样的,如果 s2 的第 j 个元素和 s3 的第 i+j 个元素相等并且 f(i,j−1) 为真,则 f(i,j) 也为真,于是可以推导出这样的动态规划转移方程:

  • 其中 p=i+j−1,边界条件为 f(0,0)=True。
  • Java 示例:
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];
    

  • C++ 示例:
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];
    
;

以上是关于数据机构与算法之深入解析“交错字符串”的求解思路与算法示例的主要内容,如果未能解决你的问题,请参考以下文章

数据机构与算法之深入解析“解码方法”的求解思路与算法示例

数据机构与算法之深入解析“柱状图中最大的矩形”的求解思路与算法示例

数据机构与算法之深入解析“完美矩形”的求解思路与算法示例

数据机构与算法之深入解析“复原IP地址”的求解思路与算法示例

数据机构与算法之深入解析“最小覆盖子串”的求解思路与算法示例

数据结构与算法之深入解析“完美数”的求解思路与算法示例