Leetcode第10题 正则表达式匹配 C++动态规划解法

这道题难为了我快一天了。看了官方的解法,不得的说,他的解法既巧妙,又复杂。实际上这道题的第五个条件很重要。
5、保证每次出现字符 * 时,前面都匹配到有效的字符。
也就是说不可能出现p字符串第一个字符为 * 的情况。
得出动态转移方程也并不难,但是这个方程里面是有几个坑的。而官方解法是采用了matches去避免。
这里我们就直接上图说明。
在这里插入图片描述
如果p[i]=* , f[i][j]=f[i-1][j] or f[i][j-2] 。这一步大家都能理解,但是坑也就在这里。
如果s=“c”,p=“a*c”。这种情况如何呢?
很显然,当匹配进行到p的字符c时,f[i=1][j=1]=f[i-1][j-1]。问题来了i-1行的时候,s是没有字符串的。是的,我们一般开辟的空间是m+1,n+1。所以第0行是我们空出来的。
如果我们正常的初始化,是对f[0][0]=1进行了初始化,但是外层循环却是从i=0开始,实际上,这里就出现了f[0][2]=1的情况。
但问题又来了,如 果我们是从0行开始的循环,我们对p[i]!= * 的时候,是要进行s[i-1]==p[j-1] || p[j-1] ==’.'的判断的,现在你让i从0开始,那不是会越界吗?所以官方的这个matches及其巧妙。
当然也要配上循环使用,我们可以看到,在第0行的时候,如果p的偶数位字符是 * ,那么将会非常神奇的出现了f[0][2]=f[0][4]…f[0][2n]=1的现象,而因为matches的限制下面的语句,但它的值已经是1了。而对于或者奇数位为 * 的情况则不会发生(你不必担心第一位是 * ,因为在前提条件5里面,已经规避了这一情况),,因为只有f[0][0]=1。而对于不为 * 的情况,第0行会直接屏蔽掉下面f[i][j] |= f[i - 1][j - 1]的运算,也不会发生越界的现象。
所以如果没有这个官方回答的精髓也就在于此,只有一个状态转移方程是无法实现这种操作的。

auto matches = [&](int i, int j) {
    if (i == 0) {
        return false;
    }
    if (p[j - 1] == '.') {
        return true;
    }
    return s[i - 1] == p[j - 1];
};

    if (p[j - 1] == '*') {
        f[i][j] |= f[i][j - 2];
        if (matches(i, j - 1)) {
            f[i][j] |= f[i - 1][j];
        }
    }
    else {
        if (matches(i, j)) {
            f[i][j] |= f[i - 1][j - 1];
        }

如果不用matches呢?当然你就要在某些句子里附加一个i>0的判断。我的写法如下。所以我个人认为这是极蠢的做法。增加了复杂度。

class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.empty())
            return s.empty();
        int i,j,m=s.size(),n=p.size();
        bool dp[m+1][n+1];
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for (i=0;i<=m;++i)
        {
            for(j=1;j<=n;++j)
                if(p[j-1]=='*')
                        dp[i][j]=(i>0&&(p[j-2]==s[i-1]||p[j-2]=='.'))&&dp[i-1][j]||dp[i][j-2];
                else 
                         dp[i][j]=i>0&&(p[j-1]==s[i-1]||p[j-1]=='.')&&dp[i-1][j-1];
        }
        return dp[m][n];
    }
};

而当我看到了下面一种解法的时候,我觉得实在是太妙了!
它在s和p前面加了一个空格,实际上加任何相同字符都可以。这样就巧妙的解决了s=“c”,p="a*c"出现的越界问题。
而且也解决了前提5里面的一个隐含条件的显示,即p的第一个字符不能为 * ,这种解法下,即使p首字符为 * 也可以通过。

class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.empty())
            return s.empty();
        s=" "+s;
        p=" "+p;
        int i,j,m=s.size(),n=p.size();
        bool dp[m+1][n+1];
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for (i=1;i<=m;++i)
        {
            for(j=1;j<=n;++j)
                {if(p[j-1]=='*')
                    dp[i][j]=(p[j-2]==s[i-1]||p[j-2]=='.')&&dp[i-1][j]||dp[i][j-2];
                else
                    dp[i][j]=(p[j-1]==s[i-1]||p[j-1]=='.')&&dp[i-1][j-1];
                }
        }
        return dp[m][n];
    }
};

那么有没有不借用这种的操作呢?答案是有的,使用带备忘录的动态规划即可。
这里先空着不谈。

以上是自底向上,当然也可以自顶向底来实现。但同样需要考虑数组越界的情况了。这可太男了~

class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.empty())
            return s.empty();
        int i,j,m=s.size(),n=p.size();
        bool dp[m+1][n+1];
        memset(dp,0,sizeof(dp));
        dp[m][n]=1;
        for (i=m;i>=0;--i)
        {
            for(j=n-1;j>=0;--j)
            {bool first=i<m&&(p[j]==s[i]||p[j]=='.');
                if(j+1<n&&p[j+1]=='*')
                    dp[i][j]=dp[i][j+2]||first&&dp[i+1][j];
                else
                    dp[i][j]=first&&dp[i+1][j+1];
                }
        }
        return dp[0][0];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值