动态规划专栏(四):路径问题

1、背景介绍

给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。

示例 1:
输入: s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac”
输出: true

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

2、背景理解

就字面意思理解,其实很简单,这里想讲的是关于它的扩展。
我们可以这样理解,s1和s2中的每个字符都代表一个动作,每个动作会产生一个与之匹配的唯一的状态,那么原背景就可以理解成,在保证s1和s2中各自动作的相对顺序不变的情况下,是否可以达到s3的状态。这样以拓展,原本简单的字符串问题,就可以建模成一个复杂的路径问题。为什么要将之称为路径问题呢,这是我自己起的,就好像每一步都会到一个新的坐标一样。

3、解题思路

有序的状态问题,都可以通过动态规划来解决。
首先,什么问题是有序的状态问题。如果我这一步的状态,是被上一步的动作所影响的,并且每个动作与状态之间存在函数依赖关系,即一个动作对应一个唯一的状态,并且动作执行的相对顺序不能被打破,那这就是有序的状态问题。

动态规划最核心的问题是状态转移方程。
该背景中,倘若给定了二维动态规划状态数组dp,那么数组dp[i][j]表示s3中前i+j个字符(包括第i+j)是否是s1中前i个字符(包括第i)和s2中前j个字符(包括第j)交错组成的,倘若要判断 dp[i][j] 为真(s3中前i+j个字符能否有s1中前i个字符以及s2中前j个字符组成),有两种情形:
1)dp[i-1][j]为真 (s3中前i+j-1个字符能否有s1中前i-1个字符以及s2中前j个字符组成),并且s3中往后移动一个字符即第i+j个字符和s1中往后移动一个字符即第i个字符相等
2)dp[i][j-1]为真 (s3中前i+j-1个字符能否有s1中前i个字符以及s2中前j-1个字符组成),并且s3中往后移动一个字符即第i+j个字符和s2中往后移动一个字符即第j个字符相等
其余情形一律为假。

注意不要把这里的第i个第j个和计算机数组中下标混淆,理解这句话时,不要去想用代码如何实现,单纯理解文字,将方案定好,模型建立成功以后,将之翻译成代码,切不可想方案的时候考虑实现,否则很容易相互影响。

4、代码详解

#include <string>
#include <vector>
using namespace std;

class Solution {
public:
    //给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的
    bool isInterleave(string s1, string s2, string s3)
    {
        //如果s3为空,即为真
        if (s3.empty())
            return true;

        int l1 = 0;
        int l2 = 0;
        int l3 = 0;
        
        //在此处判断s1、s2是否有空的情形
        if (!s1.empty()||!s2.empty())
        {
            if (!s1.empty())
                l1 = s1.length();

            if (!s2.empty())
                l2 = s2.length();
        }

        //走到此处,s3一定不为空
        l3 = s3.length();

        //如果数量不相等,直接退出
        if (l1 + l2 != l3)
            return false;

        /*****
        初始化dp数组dp[i][j]表示s3中前i+j个字符(包括第i+j)是否是s1中前
        i个字符(包括第i)和s2中前j个字符(包括第j)交错组成的
        ****/

        /**这里横纵都多声明一行一列,是因为,如果s1或者s2为空,之后代码
        实现的初始化表格中
        由于方案中dp[i][j]表示s3中前i+j个字符,那么最后一个字符在数组中
        下标未i+j-1,s1中前i个字符最后一个在数组中的下标未i-1,s2为j-1
        如果出现了s1或s2为一个字符的情况,声明的dp数组,横纵坐标一定会
        有一个为0,
        此时如果要表达s3中前1个字符和s1中前1个、s2中前0个,那就是
        dp[1][0],但是实际上编译器生成的dp空间下标是从0到0,这时候就会
        崩溃,所以多声明了一个空间,来满足方案的表达意义
        
        dp[i][j]
        |0|0|1|2|
        |1|-|-|-|
        |2|-|-|-|
        |3|-|-|-|
        **/
        vector<vector<bool>> dp(l1, vector<bool>(l2,false));

        //s2为空时
        for (int i = 1; i <= l1; ++i)
        {
            if (s1[i-1] == s3[i-1])
                dp[i][0] = true;
            else
                break;
        }

        //s1为空时
        for (int j = 1; j <= l2; ++j)
        {
            if (s2[j-1] == s3[j-1])
                dp[0][j] = true;
            else
                break;
        }

        for (int32_t i = 1; i <= l1; ++i)
        {
            for (int32_t j = 1; j <= l2; ++j)
            {
                //状态转移方程
                if ((dp[i - 1][j] && s1[i - 1] == s3[i + j - 1]) || (dp[i][j - 1] && s2[j - 1] == s3[j + i - 1]))
                {
                    dp[i][j] = true;
                }
            }
        }
        return dp[l1][l2];
    }
};

5、测试用例

建议测试时多测试边缘情况,例如s1、s2、s3各自为空的笛卡尔积结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值