每天一道LeetCode-----只可能有'.'和'*'的字符串正则匹配

来源:互联网 发布:自学编程视频网站 编辑:程序博客网 时间:2024/06/03 18:09

Regular Expression Matching

原题链接Regular Expression Matching
这里写图片描述
意思是模拟正则表达式的. *操作,判断给定的目标字符串p是否匹配源字符串s。
规则

  • ‘.’匹配任意一个字符
  • ‘*’匹配前一个字符0次或者多次,即可以删除它前面的字符

注:例子中isMatch(“ab”, “.“) == true说明”.“可以匹配的字符可以是任意组合,比如说此题中”.”匹配成”a”,那么因为 ” * ” 的前面是”.”,说明” * “可以匹配任意字符,不是说必须是前面”.”匹配的字符,也就是说”*”匹配的可以不是”.”匹配的”a”


假设s的长度为s_len,p的长度为p_len,那么可以分成比较s[0]和p[0],以及s[1 : s_len)和p[1 : p_len),再简化一点,如果p中没有” * “,可能会有” . “时
分析题目时可以分解成几种情况,

  1. (s[0] == p[0]) || (p[0] == ‘.’)。此时只需要比较s[1 : s_len)和p[1 : p_len)是否相等,递归即可实现
  2. (s[0] != p[0]) && (p[0] != ‘.’)。此时无需继续比较,返回false

如果p中有” * “,那么情况增多

  1. ((s[0] == p[0]) || (p[0] == ‘.’)) &&p[1] != ‘*’;此时和上面情况相同,比较下一个位置即可
  2. ((s[0] == p[0]) || (p[0] == ‘.’)) && p[1] == ‘‘;根据” “的规则,p[0]可以被删掉然后从p[2]开始和s[0]比较,这是一种情况。另一种是重复p[0]多次,多次的意思是p[0]后面可能有多个”p[0] * p[0] * p[0] * “,而模拟多次重复最好的方法就是不改变p的索引,仍然从p[0]开始比较s的下一个位置s[1]。
  3. ((s[0] != p[0]) && (p[0] != ‘.’)) && p[1] == ‘*’;显然直接删除p[0],从p[2]开始比较s[0]
  4. ((s[0] != p[0]) && (p[0] != ‘.’)) && p[1] != ‘*’;直接return fales,没得比了,当前这个位置就匹配不了

所以很多时候把所有情况列出来会很好的理清思路,这种当前状态可能会影响后续状态的问题是动态规划的典型问题,所以直接套用动态规划即可。
需要注意的就是一些边界条件,比方说i == s.size()的情况和j == p.size()的情况,又或者是j + 1 >= p.size()的情况,这种情况p[j + 1]不存在,就没办法判断后面的字符是否是’*’。


首先是递归的动态规划,dp[i][j]的意思是s[i : s_len)是否和p[j : p_len)匹配,递归时需要有三种状态

  1. 1,true
  2. 0,false
  3. -1,还没有被考虑
class Solution {public:    bool isMatch(string s, string p) {        /* 创建时数量多创建一个,即s.size() + 1和p.size() + 1         * 原因在于递归时i, j可能等于s.size()和p.size()         */        std::vector<std::vector<int> > dp(s.size() + 1, std::vector<int>(p.size() + 1, -1));        return judge_match(0, 0, s, p, dp) == 1;    }private:    int judge_match(int i, int j, const std::string& s, const std::string& p, std::vector<std::vector<int> >& dp)    {        /* 如果dp[i][j] != -1,说明s[i : s_len)是否和p[j : p_len)匹配已经判断过,直接返回          * 这个位置就容易越界,是上面创建vector时数量加1的原因         */        if(dp[i][j] != -1)            return dp[i][j];        /* 如果s和p都已经到达尾端,s没有字符需要匹配,p也没有可匹配的字符,直接返回true          * 为什么需要判断p也到尾端?         * 如果p未到尾端,说明p可能字符比s多,同样可能是无法匹配的,所以不能直接返回         * 只是可能无法匹配的原因         * p后面的每一个字符都可以被后面的'*'删掉,从而和s完全匹配         */        if(j >= p.size() && i >= s.size())            return 1;        /* 如果p到达尾端而s未到,说明s剩余的字符p已经没有字符可以匹配了,返回false */        else if(j >= p.size() && i < s.size())            return 0;        bool ans;        bool first_match = (i < s.size()) && ((s[i] == p[j]) || (p[j] == '.'));        /* 判断p[j]后面的字符是否是'*',从而有删掉p[j]和重复p[j]两种可能         * 注:重复p[j]的前提是s[i] == p[j] || p[j] == '.',否则s[i]无法匹配,只能删除p[j]         */        if(j + 1 < p.size() && p[j + 1] == '*')            ans = (first_match && judge_match(i + 1, j, s, p, dp) == 1) || (judge_match(i, j + 2, s, p, dp) == 1);        else            ans = (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1);        dp[i][j] = ans ? 1 : 0;        return dp[i][j];    }};

然后是非递归的,非递归的动态规划很重要,真…的…很重要
逐层向目标接近,有个选择两层for循环的界限的技巧,初始时是离目标最远的状态
比如说如果目标是dp[0][0],那么初始时就是dp[s_len - 1][p_len - 1],如果目标是dp[0][p_len],那么初始时就是dp[s_len][0]
但是,需要考虑边界情况,比如说本题就需要考虑dp[s_len][0],dp[s_len][1]…dp[s_len][p_len - 1],原因在递归中也体现了,就是如果s到了尾端而p没有到,也是有可能匹配成功的(p后面的字符全可以删除时),所以初始时是dp[s_len][p_len - 1]

class Solution {public:    bool isMatch(string s, string p) {        /* 数量加1的原因仍然是防止越界 */        std::vector<std::vector<bool> > dp(s.size() + 1, std::vector<bool>(p.size() + 1, false));        /* 上面递归时的边界判断,都到尾端时代表匹配 */        dp[s.size()][p.size()] = true;        /* i是从s.size()开始的,原因是dp[s_len][0] ... dp[s_len][p_len - 1]仍然可能是true */        for(int i = s.size(); i >= 0; --i)        {            for(int j = p.size() - 1; j >= 0; --j)            {                bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '.');                if(j + 1 < p.size() && p[j + 1] == '*')                    dp[i][j] = (first_match && dp[i + 1][j]) || (dp[i][j + 2]);                else                    dp[i][j] = (first_match && dp[i + 1][j + 1]);            }        }        return dp[0][0];    }};


Wildcard Matching

原题链接Wildcard Matching
这里写图片描述
这道题和上面的基本一样了,不同点就是’*’的规则有改变,代码如下
没什么特别的,也分递归和非递归,判断的时候多了一个条件,就是’*’可以表示空字符

class Solution {public:    bool isMatch(string s, string p) {        std::vector<std::vector<bool> > dp(s.size() + 1, std::vector<bool>(p.size() + 1, false));        dp[s.size()][p.size()] = true;        for(int i = s.size(); i >= 0; --i)        {            for(int j = p.size() - 1; j >= 0; --j)            {                bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '?' || p[j] == '*');                /* dp[i][j + 1]是让'*'表示空字符 */                if(p[j] == '*')                    dp[i][j] = (first_match && dp[i + 1][j]) || (first_match && dp[i + 1][j + 1]) || (dp[i][j + 1]);                else                    dp[i][j] = (first_match && dp[i + 1][j + 1]);            }        }        return dp[0][0];    }/*    bool isMatch(string s, string p) {        std::vector<std::vector<int> > dp(s.size() + 1, std::vector<int>(p.size() + 1, -1));        return judge_match(0, 0, s, p, dp);    }private:    int judge_match(int i, int j, const std::string& s, const std::string& p, std::vector<std::vector<int> >& dp)    {        if(dp[i][j] != -1)            return dp[i][j];        if(j >= p.size() && i >= s.size())            return 1;        else if(j >= p.size() && i < s.size())            return 0;        bool ans;        bool first_match = (i < s.size()) && (s[i] == p[j] || p[j] == '?' || p[j] == '*');        if(p[j] == '*')            ans = (first_match && judge_match(i + 1, j, s, p, dp) == 1) || (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1) || (judge_match(i, j + 1, s, p, dp) == 1);        else            ans = (first_match && judge_match(i + 1, j + 1, s, p, dp) == 1);        dp[i][j] = ans ? 1 : 0;        return dp[i][j];    }    */};
阅读全文
0 0