2021-9-1 97. 交错字符串(动态规划+优化)

注:

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

题解:
方法一 动态规划
思路与算法

解决这个问题的正确方法是动态规划。 首先如果 |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−1) 为真,则 f(i, j)f(i,j) 也为真。

于是我们可以推导出这样的动态规划转移方程:

f(i, j) = [f(i - 1, j) &&s1(i - 1) = s3(i+j-1)] || [f(i, j - 1) && s2(j - 1) = s3(i+j-1)]

复杂度分析
时间复杂度:O(nm),两重循环的时间代价为 O(nm)。
空间复杂度:O(nm)。

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n=s1.size();
        int m=s2.size();
        int t=s3.size();
        if(n+m!=t){
            return false;
        }
        vector<vector<int>> dp(n+1,vector<int>(m+1));
        dp[0][0]=1;
        // 第0行:s1前缀长度0 + s2前缀长度i,能否交错组成s3前缀长度i
        for(int j=1;j<=m;j++){
            dp[0][j]=(dp[0][j-1]&&s2[j-1]==s3[0+j-1]);
        }
        // 第0列:s1前缀长度i + s2前缀长度0,能否交错组成s3前缀长度i
        for(int i=1;i<=n;i++){
            dp[i][0]=(dp[i-1][0]&&s1[i-1]==s3[i+0-1]);
        }
        // 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                // 考虑s3的最后一个字符s3[i+j-1]来自哪里?做状态转移:
                // 如果来自s1[i-1],则dp[i][j] 为:s1前缀长度i-1 + s2前缀长度j 能否交错组成s3前缀长度i+j-1,即:dp[i-1][j]
                // 如果来自s2[j-1],则dp[i][j] 为:s1前缀长度i + s2前缀长度j-1 能否交错组成s3前缀长度i+j-1,即:dp[i][j-1]
                dp[i][j]=(s1[i-1]==s3[i+j-1]&&dp[i-1][j])||(s2[j-1]==s3[i+j-1]&&dp[i][j-1]);
            }
        }
        return dp[n][m];
    }
};

在不单独考虑第一行以及第一列的情况下,以上写法可以合并为

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n=s1.size();
        int m=s2.size();
        int t=s3.size();
        if(n+m!=t){
            return false;
        }
        vector<vector<int>> dp(n+1,vector<int>(m+1));
        dp[0][0]=1;
        // 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++){
                //注意第二个式子用了或操作,如果不加上这个或操作,假设dp的值通过s1计算完成之后变成了True,但是通过s2之后又变成了false,对于后面的运算会产生影响
                if(i>0){
                    dp[i][j]=(s1[i-1]==s3[i+j-1]&&dp[i-1][j]);
                }
                if(j>0){
                    dp[i][j]|=(s2[j-1]==s3[i+j-1]&&dp[i][j-1]);
                }
            }
        }
        return dp[n][m];
    }
};

方法二 优化
由于 第 i 行只和第i−1 行相关,虽然有两层for循环,但是内层循环每次遍历都只用一维数组dp,dp 存放的就是第 i-1 行的值,所以可以直接将 i 所在维度去掉。

dp[i][j] = (s1[i - 1] == s3[i+j-1] && dp[i - 1][j])
变为
dp[j] = (s1[i - 1] == s3[i+j-1]&&dp[j])

dp[i][j] |= (s2[j - 1] == s3[i+j-1] && dp[i][j - 1])
 变为
dp[j]|=(s2[j-1]==s3[i+j-1]&&dp[j-1])

复杂度分析
时间复杂度:O(nm),两重循环的时间代价为O(nm)。
空间复杂度:O(m),即 s2的长度。

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n=s1.size();
        int m=s2.size();
        int t=s3.size();
        if(n+m!=t){
            return false;
        }
        //dp用来表示每行的状态
        vector<int> dp(m+1);
        dp[0]=1;
        // 普通位置 dp[i][j]:s1前缀长度i + s2前缀长度j,能否交错组成s3前缀长度i+j
        for(int i=0;i<=n;i++){
            for(int j=0;j<=m;j++){
                if(i>0){
                    dp[j] =(s1[i-1]==s3[i+j-1]&&dp[j]);
                }
                if(j>0){
                    dp[j]|=(s2[j-1]==s3[i+j-1]&&dp[j-1]);
                }
            }
        }
        return dp[m];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一道比较有意思的字符串题目,我们可以使用动态规划来解决。使用 Kotlin 语言来实现,具体思路如下: 定义一个二维布尔数组 `dp[i][j]`,表示 s1 的前 i 个字符和 s2 的前 j 个字符能否交错组成 s3 的前 i+j 个字符。 初始状态:当 s1 和 s2 都为空时,s3 也为空,因此 `dp[0][0] = true`。 状态转移:对于每个 `dp[i][j]`,我们有两种选择: - 如果 `s1[i-1] == s3[i+j-1]`,则可以使用 `s1[i-1]` 来匹配 `s3[i+j-1]`,此时需要判断 `dp[i-1][j]` 是否为 true。 - 如果 `s2[j-1] == s3[i+j-1]`,则可以使用 `s2[j-1]` 来匹配 `s3[i+j-1]`,此时需要判断 `dp[i][j-1]` 是否为 true。 最终的结果为 `dp[s1.length][s2.length]`。 完整的代码如下: ```kotlin fun isInterleave(s1: String, s2: String, s3: String): Boolean { if (s1.length + s2.length != s3.length) { return false } val dp = Array(s1.length + 1) { BooleanArray(s2.length + 1) } // 初始化状态 dp[0][0] = true // 初始化第一行 for (j in 1..s2.length) { dp[0][j] = dp[0][j-1] && s2[j-1] == s3[j-1] } // 初始化第一列 for (i in 1..s1.length) { dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1] } // 状态转移 for (i in 1..s1.length) { for (j in 1..s2.length) { 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[s1.length][s2.length] } ``` 我们可以使用以下代码测试这个函数: ```kotlin fun main() { val s1 = "aabcc" val s2 = "dbbca" val s3 = "aadbbcbcac" val res = isInterleave(s1, s2, s3) println(res) } ``` 输出结果为: ``` true ``` 这说明我们的代码已经正确地判断了 s3 是否是由 s1 和 s2 交错组成的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值