【LeetCode】10. Regular Expression Matching【动态规划&递归】

       对于p字符串有点、字母、点*、字母*四种元素,点匹配任意一个字母,字母匹配相同的一个字母,点*匹配任意字母(可以是任意不同字母,例如.*匹配abc),字母*匹配连续任意个相同字母,值得注意的是*的任意包括0个。由于*可以匹配任意个,造成检验s和p是否完全匹配的时候难以确定究竟*匹配几个字母合适,这正是本题的关键点。题意简单粗暴,看一下原题,然后分析一下如何处理。


Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

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

Note:

  • s could be empty and contains only lowercase letters a-z.
  • p could be empty and contains only lowercase letters a-z, and characters like . or *.

Example 1:

Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".

Example 3:

Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".

Example 4:

Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab".

Example 5:

Input:
s = "mississippi"
p = "mis*is*p*."
Output: false

 


 

一、动态规划

开始前的准备工作

      当p中的a*遇到s中的aaa的时候,由于事先不知道那哪种匹配可能是正确的或者有几种是正确的,应该尝试匹配空、a、aa、aaa等全部可能的情况,而动态规划实际上就是一种穷举所所有情况的好方法。假设s有m个元素,p有n个元素,这里我选择建立dp[m+1][n+1]数组,之所以多1排1列是为了处理首排首列的时候方便一些,减少特殊情况的单独列出。横排m+1代表空白一个首元素+s中的m个元素,竖排n+1代表空白一个元素+p中的n个元素,相当于给s和p开头加了一个可有可无的空白元素。先处理匹配s前面加的空白元素匹配问题,p可以使用任意空白去匹配(加的空白元素和.*和字母*等),如果匹配,对应位为true。

然后重点处理s中的m个元素匹配

      每一次匹配结果受三个方向(dp二维数组)影响,例如aab和c*a*b,假如当前在匹配前者的b和后者的a。 


1、上方向代表s当前元素和p上一个元素的匹配情况(即b和*),如果当前p元素是*,而*的前一个元素匹配了s的当前元素,则加上*也一定匹配,这种匹配方式等于用字母*或.*只匹配一个元素。上方向也可以代表s当前元素和p上两个元素匹配情况(即a和c是否匹配),这种匹配方式等于把字母*或.*一并拿出来,匹配0个元素,如果匹配,则加上字母*或.*这个空白也一定匹配。如果当前p元素不是*不用参考此方向。

2、左方向代表s上一个元素和p当前元素的匹配情况(即a和b),如果当前p元素是*,而字母*或.*已经匹配s的上一个元素,又能匹配s的当前元素,就可以把字母*或.*的匹配数量延长一个,到达2或更多。至此就包括了字母*或.*匹配0个、1个或多个元素的全部情况。

3、左上方向代表s上一个元素和p上一个元素的匹配情况(即a和*),如果当前元素是字母或者.,也就是说不是*,则若此处匹配,必须是p的上一个元素匹配s的上一个元素且p的当前元素也匹配s的当前元素。


检查一遍已经包括了所有的情况,没有遗漏。其中1和2处理*的匹配,这是最复杂的情况,3处理字母和.的匹配,比较容易。

对照一下代码和注释,可以把代码和以上三种方向对照一下。

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(),n = p.size();
        vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
        dp[0][0] = true;
        for (int j = 1;j < n && p[j] == '*';j+=2)
            dp[0][j+1] = true;
        for (int i = 0;i < m;i++)
            for (int j = 0;j < n;j++){
                if (p[j]!='*')
                    //如果是字母或.只受左上方向影响,即方向3
                    dp[i+1][j+1] = p[j]=='.' ? dp[i][j] : (dp[i][j] && (p[j]==s[i]));
                else
                    //如果是*则受左和上两个方向影响,即方向1和2,有三个或分别对应上述三种情况
                    dp[i+1][j+1] = dp[i+1][j-1] || dp[i+1][j] || (dp[i][j+1]&&((p[j-1]==s[i])||p[j-1]=='.'));
            }
        return dp[m][n];
    }
};

二、递归

对于*可以匹配任意数量元素的不确定性也可以用递归处理,可不可行试过才知道,通过深入下一层递归判断。所以有三大类情况:匹配、不匹配、不知道需要深入判断。其中细分情况比较多,我放在代码注释里一一解释。记住一点,只有匹配到s和p都没有剩余元素了才算成功。

bool match(string& s,int i,string& p,int j){
    if (i==s.size() && j==p.size())    //如果s和p都没有剩余元素了,则匹配成功
        return true;
    if (j==p.size())    //如果s还有元素但p没了,则匹配失败
        return false;
    if (j+1<p.size() && p[j+1]=='*'){    //如果p的下一个元素是*,当作整体处理,分为.*和字母*讨论
        if(p[j]=='.'){    //如果是.*
            for (int pos = i;pos <= s.size();pos++)    //穷举匹配0、1、多个元素的情况,有一个匹配就成功,都失败则彻底失败
                if (match(s,pos,p,j+2))    //注意这里不能return match(),否则穷举不了后面的情况
                    return true;
            return false;
        }
        else{    //如果是字母*
            if(match(s,i,p,j+2))    return true;    //匹配0个元素,等于跳过字母*,同样不能return match()
            for (int pos = i;pos<s.size()&&s[pos]==p[j];pos++)    //匹配1、多个元素,比.*多考虑的是只有当前的一个匹配上了,才能考虑匹配更多的元素
                if (match(s,pos+1,p,j+2))
                    return true;
            return false;
        }
    }
    else{    //如果是字母或.,匹配上了的话深入下一层看能否成功,此处终于可以return match()了
        if (i<s.size() && (s[i]==p[j] || p[j]=='.'))
            return match(s,i+1,p,j+1);    
        else
            return false;
    }
}

class Solution {
public:
    bool isMatch(string s, string p) {
        return match(s,0,p,0);
    }
};

个人认为动态规划代码简洁漂亮,内涵却不简单,两句话写尽人世铅华,递归写的比较粗糙,又臭又长,胜在情况分类比较清晰。最后欢迎大家留言讨论,如有错误或改进还请不吝赐教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章