《LeetBook》leetcode题解(10): Regular Expression Matching——DP解决正则匹配

我如今在作一个叫《leetbook》的免费开源书项目,力求提供最易懂的中文思路,目前把解题思路都同步更新到gitbook上了,须要的同窗能够去看看
书的地址:https://hk029.gitbooks.io/leetbook/
c++

这里写图片描述

010. Regular Expression Matching

问题

Implement regular expression matching with support for ‘.’ and ‘*’.git

‘.’ Matches any single character.
‘*’ Matches zero or more of the preceding element.算法

The matching should cover the entire input string (not partial).express

The function prototype should be:
bool isMatch(const char *s, const char *p)数组

Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → truemarkdown

思路

这里面最复杂的操做是”*”,这是个很可恶的操做,由于你永远不知道它多长。可是有一点,”*”不会单独出现,它必定是和前面一个字母或”.”配成一对。当作一对后”X*”,它的性质就是:要不匹配0个,要不匹配连续的“X”测试

题目的关键就是如何把这一对放到适合的位置。ui

考虑一个特殊的问题:
状况1:
“aaaaaaaaaaaaaaaa”
“a*aa*”
this

状况2:
“aaaaaaaaaaaaaaaa”
“a*ab*”
spa

在不知道后面的状况的时候,我如何匹配a*?

  • 最长匹配
    显然不合适,这样后面的a就没法匹配上了

  • 匹配到和后面长度同样的位置,好比状况1,就是留3个a不匹配,让后面3个字母尝试去匹配。
    这样看似合适,可是遇到状况2就不行了。

  • 回溯,每种”*”的状况,看哪一种状况能成功,若是其中出现了问题,立刻回溯,换下一种状况

思路1——回溯

若是“*”很差判断,那我大不了就来个暴力的算法,把“*”的全部可能性都测试一遍看是否有知足的,用两个指针i,j来代表当前s和p的字符。
咱们采用从后往前匹配,为何这么匹配,由于若是咱们从前日后匹配,每一个字符咱们都得判断是否后面跟着“*”,并且还要考虑越界的问题。可是从后往前没这个问题,一旦遇到“*”,前面必然有个字符。

  • 若是j遇到”*”,咱们判断s[i] 和 p[j-1]是否相同,
    • 若是相同咱们能够先尝试匹配掉s的这个字符,i–,而后看以后能不能知足条件,知足条件,太棒了!咱们就结束了,若是中间出现了一个不知足的状况,立刻回溯到不匹配这个字符的状态。
    • 无论相同不相同,都不匹配s的这个字符,j-=2 (跳过“*”前面的字符)
if(p[j-1] == '.' || p[j-1] == s[i])
    if(myMatch(s,i-1,p,j))
        return true;
    return myMatch(s,i,p,j-2);
  • 若是j遇到的不是“*”,那么咱们就直接看s[i]和p[j]是否相等,不相等就说明错了,返回。
if(p[j] == '.' || p[j] == s[i])
         return myMatch(s,i-1,p,j-1);
    else return false;
  • 再考虑退出的状况
    • 若是j已经<0了说明p已经匹配完了,这时候,若是s匹配完了,说明正确,若是s没匹配完,说明错误。
    • 若是i已经<0了说明s已经匹配完,这时候,s能够没匹配完,只要它还有”*”存在,咱们继续执行代码。

因此代码应该是这样的:

class Solution {
public:
    static const int FRONT=-1;
    bool isMatch(string s, string p) {
        return myMatch(s,s.length()-1,p,p.length()-1);
    }
    bool myMatch(string s, int i, string p,int j)
    {
        if(j == FRONT)
            if(i == FRONT)    return true;
        else return false;
        if(p[j] == '*')
        {
            if(i > FRONT && (p[j-1] == '.' || p[j-1] == s[i]))
                if(myMatch(s,i-1,p,j))
                    return true;
            return myMatch(s,i,p,j-2);
        }
        if(p[j] == '.' || p[j] == s[i])
            return myMatch(s,i-1,p,j-1);
        return false;
    }
};

思路2——DP

DP的话,确定要用空间换时间了,这里用 monkeyGoCrazy 的思路:用2维布尔数组,dp[i][j]的含义是s[0-i] 与 s[0-j]是否匹配。

  1. p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1]
  2. If p.charAt(j) == ‘.’ : dp[i][j] = dp[i-1][j-1];
  3. If p.charAt(j) == ‘*’:
    here are two sub conditions:
    • if p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
    • if p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == ‘.’:
      dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
      dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
      dp[i][j] = dp[i][j-2] // in this case, a* counts as empty

这里用的bool数组比较巧妙,初始化为true。前两种状况好理解,若是匹配成功就维持以前的真假值。程序的目的是看真值能不能传递下去。若是遇到三种状况,咱们就看哪一种状况有真值能够传递,就继续传递下去。

图示

我用excel本身跑了下代码,画了一下示意图,下面橘黄色表示正常匹配了,蓝色表示“*”匹配空串。能够看出真值是如何传递下去的。
这里写图片描述

这里写图片描述

这里写图片描述

初始化

dp[0][0] = true;
//初始化第0行,除了[0][0]全为false,毋庸置疑,由于空串p只能匹配空串,其余都无能匹配
for (int i = 1; i <= m; i++) dp[i][0] = false; //初始化第0列,只有X*能匹配空串,若是有*,它的真值必定和p[0][j-2]的相同(略过它以前的符号) for (int j = 1; j <= n; j++) dp[0][j] = j > 1 && '*' == p[j - 1] && dp[0][j - 2];

代码执行

for(int i = 1;i <= m;i++)
{
    for(int j = 1;j <= n;j++)
    {
        //这里j-1才是正常字符串中的字符位置
        //要不*当空,要不就只有当前字符匹配了*以前的字符,才有资格传递dp[i-1][j]真值
        if(p[j-1] == '*')
            dp[i][j] = dp[i][j-2] || (s[i-1] == p[j-2] || p[j-2] == '.') && dp[i-1][j];
        else 
        //只有当前字符彻底匹配,才有资格传递dp[i-1][j-1] 真值
            dp[i][j] = (p[j-1] == '.' || s[i-1] == p[j-1]) && dp[i-1][j-1];
    }
}

返回值

return dp[m][n]

完整代码

class Solution
{
public:
    static const int FRONT=-1;
    bool isMatch(string s, string p)
    {
        int m = s.length(),n = p.length();
        bool dp[m+1][n+1];
        dp[0][0] = true;
//初始化第0行,除了[0][0]全为false,毋庸置疑,由于空串p只能匹配空串,其余都无能匹配
        for (int i = 1; i <= m; i++)
            dp[i][0] = false;
//初始化第0列,只有X*能匹配空串,若是有*,它的真值必定和p[0][j-2]的相同(略过它以前的符号)
        for (int j = 1; j <= n; j++)
            dp[0][j] = j > 1 && '*' == p[j - 1] && dp[0][j - 2];

        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if (p[j - 1] == '*')
                {
                    dp[i][j] = dp[i][j - 2] || (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j];

                }
                else   //只有当前字符彻底匹配,才有资格传递dp[i-1][j-1] 真值
                {
                    dp[i][j] = (p[j - 1] == '.' || s[i - 1] == p[j - 1]) && dp[i - 1][j - 1];

                }
            }
        }
        return dp[m][n];
    }
};
相关文章
相关标签/搜索