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

举报
Serendipity·y 发表于 2022/02/17 00:07:57 2022/02/17
【摘要】 一、题目要求 给定三个字符串 s1、s2、s3,请帮忙验证 s3 是否是由 s1 和 s2 交错组成的。两个字符串 s 和 t 交错的定义与过程如下,其中每个字符串都会被分割成若干非空子字符串: ...

一、题目要求

  • 给定三个字符串 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

  
 
  • 1
  • 2
  • 示例 2:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
输出:false

  
 
  • 1
  • 2
  • 示例 3:
输入:s1 = "", s2 = "", s3 = ""
输出:true

  
 
  • 1
  • 2
  • 提示:
    • 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]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

② 动态规划(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];
    }
}

  
 
  • 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
  • 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];
    }
};

  
 
  • 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

文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/122951063

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。