leetcode 正则表达式匹配 回溯法与动态规划详解

题目

请实现一个函数用来匹配包含’. ‘和’'的正则表达式。模式中的字符 ’ . ’ 表示任意一个字符,而 ’ * ’ 表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 " aaa " 与模式 " a.a " 和 " abaca " 匹配,但与"aa.a"和"aba"均不匹配。

示例 1:

输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

示例 2:

输入:
s = “aa”
p = “a*”
输出: true
解释: 因为 ‘*’ 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 ‘a’。因此,字符串 “aa” 可被视为 ‘a’ 重复了一次。

示例 3:

输入:
s = “ab”
p = “."
输出: true
解释: ".
” 表示可匹配零个或多个(’*’)任意字符(’.’)。

示例 4:

输入:
s = “aab”
p = “cab”
输出: true
解释: 因为 ‘*’ 表示零个或多个,这里 ‘c’ 为 0 个, ‘a’ 被重复一次。因此可以匹配字符串 “aab”。

示例 5:

输入:
s = “mississippi”
p = “misisp*.”
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 ,无连续的 '’。

一、回溯法

本题目我们实现函数isMatch(string s, string p):通过不停的剪去 s 和 p 相同的首部,直到某一个或两个都剪为空,就可以得到结论(当然需要结合所有剪空的情况)。

首先我们从没有 ’ * ’ 的最简单情况来看,这时是不是只需要扫一遍 s 和 p ,从首部比较对应的元素是否相同即可,如果相同就可以剪去,比较下一个即:

//第i个下标位置元素的比较,'.'代表任何元素
s[i] == p[i] || p[i] == '.'

那么现在添加 ’ * ’ ,需要注意题目要求,此时 ’ * ’ 前的元素可以出现0次或者多次,那么注意在检测到 p 中第 i 个元素的下一个元素为 ’ * ’ 时,此时就会有两种情况:

  • p 的第 i 个元素在 s 中出现0次;此时,我们保持 s 不变,将 p 剪2个元素,继续调用isMatch。如:s = “bb”, p = “a*bb”,将 p 的首部2个元素剪去,得到 p = “bb”,继续比较;
  • p 的第 i 个元素在 s 中出现1次或多次;此时,比较 i 元素和 s 的首元素,如果相同,剪去 s 的首元素,保持 p 不变继续调用isMatch。如:s = “aabb”,p = “a*bb”,那么比较首元素相同,再剪去 s 首元素得 s = “abb”,在调用isMatch比较。

当然会出现这种情况:s = “abb”, p = “a*abb”;

按 1 来说,p 剪去2个元素,s = “abb”, p = “abb”,成功;

按2来说,s = “bb”, p = “a*abb”,失败。为什么会出现这种情况呢,

因为上面的两种情况都是我们假设的,第1 种情况是我们假设p 的第 i 个元素在 s 中出现0次,第2种情况是我们假设p 的第 i 个元素在 s 中出现1次或多次,所以真实情况具体是哪种,我们不知道。

所以,我们会把所有情况全试一遍,就得到结果了。

class Solution {
public:
    bool isMatch(string s, string p) {
        // 在p为空之后,只需要查看s是否为空即可
        if(p.empty()) {
            return s.empty();
        }
        // 查看首元素是否一致
        bool first_match = !s.empty() && (s[0] == p[0] || p[0] == '.');
        // 如果下一个字符是'*'
        if(p.size() >= 2 && p[1] == '*') {
            return (bool)(isMatch(s, p.substr(2)) || (first_match && isMatch(s.substr(1), p)));
        }
        // 一般情况
        else {
            return bool(first_match && isMatch(s.substr(1), p.substr(1)));
        }
    }
};

二、动态规划

dp[i][j]表示:s 的前 i 和字符和 p 的前 j 个字符是否匹配

这里构建dp依然考虑回溯法的那几种情况

初始化dp[0][0] = true,因为两个字符串都为空,那肯定匹配

dp[0][0] = true;

接着还要手动初始化,就是s为空的时候即dp[0][j],
因为s为空了,那么意味着p最终也能为空才能匹配上。
那么p能匹配的情况就类似于 p = " a* "这样的形式,这时的情况就是 p[j-1],即 p 的第 j 个字符为 ’ * ',此时可以表示前面一个字符可以被消除,那么就需要判断p[0] - p[j-3]这前 j-2 个字符能否被匹配

for(int j = 2; j <= p.size(); j++) {
	dp[0][j] = p[j-1] == '*' && dp[0][j-2];
}

接着便是构建状态转移方程

bool first_match(string s, string p, int i, int j) {
       return s[i] == p[j] || p[j] == '.';
   }
for(int i = 0; i < s.size(); i++) {
     for(int j = 0; j < p.size(); j++) {
          if(p[j] == '*') {
              dp[i+1][j+1] = dp[i+1][j-1] || (first_match(s, p, i, j-1) && dp[i][j+1]);
          }else {
              dp[i+1][j+1] = first_match(s, p, i, j) && dp[i][j];
          }
      }
}

双层循环
注意是dp[i+1][j+1],因为i+1恰好表示前i个元素
先看 p[j] == ’ * '的情况,根据回溯法上面的解析

  • p 的第 i 个元素在 s 中出现0次;此时,我们保持 s 不变,将 p 剪2个元素,继续调用isMatch。如:s = “bb”, p = “a*bb”,将 p 的首部2个元素剪去,得到 p = “bb”,继续比较;
  • p 的第 i 个元素在 s 中出现1次或多次;此时,比较 i 元素和 s 的首元素,如果相同,剪去 s 的首元素,保持 p 不变继续调用isMatch。如:s = “aabb”,p = “a*bb”,那么比较首元素相同,再剪去 s 首元素得 s = “abb”,在调用isMatch比较。

对于第1种情况,是将 p 的前两个元素剪去,接着比较,那么就是 dp[i+1][j-1]
对于第2种情况,比较 s 的 i 元素和 p 的 j-1 处的元素,如果相同,剪去 s 的一个元素,那么就由i+!变为i,那么就是 first_match(s, p, i, j-1) && dp[i][j+1]

最后返回dp[s.size()][p.size()]即可。

class Solution {
public:
    bool isMatch(string s, string p) {
        vector<vector<bool> >dp(s.size()+1, vector<bool>(p.size()+1, false));
        dp[0][0] = true;
        for(int i = 2; i <= p.size(); i++) {
            dp[0][i] = p[i-1] == '*' && dp[0][i-2];
        }
        for(int i = 0; i < s.size(); i++) {
            for(int j = 0; j < p.size(); j++) {
                if(p[j] == '*') {
                    dp[i+1][j+1] = dp[i+1][j-1] || (first_match(s, p, i, j-1) && dp[i][j+1]);
                }else {
                    dp[i+1][j+1] = first_match(s, p, i, j) && dp[i][j];
                }
            }
        }
        return dp[s.size()][p.size()];
    }
    bool first_match(string s, string p, int i, int j) {
        return s[i] == p[j] || p[j] == '.';
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值