leetcode习题解答:10. Regular Expression Matching

来源:互联网 发布:域名注册好了怎么使用 编辑:程序博客网 时间:2024/05/16 12:27

难度:Hard

描述:

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).The function prototype should be:bool isMatch(const char *s, const char *p)Some examples:isMatch("aa","a") → falseisMatch("aa","aa") → trueisMatch("aaa","aa") → falseisMatch("aa", "a*") → trueisMatch("aa", ".*") → trueisMatch("ab", ".*") → trueisMatch("aab", "c*a*b") → true
思路:

真的难,最开始的思路是循环用p字符串中的每一部分一步一步来匹配s字符串,在*这里卡住了,这种方法会贪婪匹配所有的X*的X,如果

p为:a*a    s为:aaa

那么就完全匹配不了了,需要后面的字符信息来完成匹配,因为你不知道a*到底对应几个a。

想了半天,参考了其他人的答案说下我的理解:

1.递归法

递归中最难处理的是遇到X*匹配X,你说你要匹配几个呢,没有上下文的支持你是不知道的,怎么办?

诀窍在于当用X* 匹配 X时分两种情况:

1.直接不用X*匹配,跳过,相当于生成了X^0,举个例子:在p=a*a ,s=a时,你就会让a* = a^0对不对?

这就是模拟这种情况,直接在让p的计数器+2(也可以是-2,看你怎么递归),跳过这个X*,然后s计数器不变,进入下一轮的递归,这个递归返回为真,那么说明

这么跳过去是对的,X*应该匹配个空子串。


2.但是上面的递归返回为假时怎么办呢?那么就说明要完成匹配X*不能只匹配个空子串,它需要至少匹配1个X,这时将s的计数器+1,p计数器不变,进入下一轮递归


如果X*没能匹配到X,那么直接p计数器+2,下一轮递归。


对于退出,只要当p 完全被匹配完后,开始判断剩下的s长度,如果也匹配完了,说明成功。s还剩下就说明失败

然后s被匹配完后,p还剩下的话,需要在处理p的字符时判断一下是不是要返回假了。

代码:

class Solution {public:
    bool match(string& s, int i, string &p, int j){  //这个程序是从后往前匹配的,这样不用担心*导致的各种越界校验        if (j < 0){            return (i < 0);        }        if (p[j] != '*'){            if (i < 0)return false;  //不是X*而已经空了,说明匹配失败            if (s[i] == p[j]|| p[j] == '.') return match(s,i-1,p,j-1);            else return false;        } else if (p[j] == '*'){            if (i < 0) return match(s,i,p,j-2);   //是X*,可以让它匹配X^0,从而继续往下匹配            if (p[j-1] == s[i] || p[j-1] == '.'){                if (match(s,i,p,j-2)) return true;   //如果让X*匹配X^0                else                    return match(s,i-1,p,j);        //不行,就让他匹配至少一个X            }else{                return match(s,i,p,j-2);           //没匹配到X,计数器+2            }        }    }    bool isMatch(string s, string p) {        return match(s,s.size()-1,p,p.size());    }};


2.动态规划:

上面的解法比较容易理解,但是效率较低。

这个问题可以使用动态规划解决。

首先要找出状态转移的方程

dp[i][k]代表p{0,1.,2.....k-1}和s{0,1,2.....i-1}的状态。bool值

初始化,dp[0][0]=true,空串匹配空串,当然为真

dp[0][i],除了i==0,为假,其他都是如果p[i-1] == '*',那么dp[0][j] = dp[0][i-2], 如果p[i-1] != '*',那dp[0][i] = false

我们可以通过dp[i-1][k-1]知道dp[i][k]的状态吗?

可以有下面几种情况:

p[k-1]不为*,那么只要知道dp[i-1][k-1]的值,然后看p[k-1]等不等于s[i-1]就行

p[k-1]为*,

这就比较麻烦,有两种情况,p[k-2]+p[k-1]不匹配任何字符,匹配个空子串 ,dp[i][k]==dp[i][k-2]

匹配了字符串,p[k-2] ==‘.' or s[i-1],且 需要 dp[i-1][k]的值,因为光有s[i-1] == p[k-2]是不够说明的,dp[i-1][k]代表这个p的k个字符匹配了之前的i-1个字符的s

上面说的p[k-1]为*时的两种情况不互斥,用||连起来

最后返回dp[i][k]

代码:

class Solution {public:    bool isMatch(string s, string p) {        bool dp[1000][1000];        dp[0][0] = true;        for (int i = 1; i <= s.size(); i++){            dp[i][0] = false;        }        dp[0][1] = false;        for (int i = 2; i <= p.size(); i++){            if (p[i-1] == '*') dp[0][i] = dp[0][i-2];            else dp[0][i] = false;        }        for (int i = 1; i <= s.size(); i++){            for (int k = 1; k <= p.size();k++){                if (p[k-1] != '*'){                    dp[i][k] = (p[k-1] == s[i-1] || p[k-1] =='.') && dp[i-1][k-1];                } else {                    dp[i][k] = dp[i][k-2] || (dp[i-1][k] && (p[k-2] == '.' || s[i-1] == p[k-2]));   //要么匹配空串,要么s[i-1] == p[k-2],且该字符前的
s可以和当前的p匹配,以X*举例说明,这情况下说明之前s已经有了1个以上的X                }            }        }        return dp[s.size()][p.size()];    }};


原创粉丝点击