[LeetCode] 10、正则表达式匹配

本文探讨了正则表达式的高级应用,通过分析一个具体的字符串匹配问题,详细讲解了使用递归和动态规划两种方法解决正则表达式匹配问题的策略。文章提供了详细的代码示例,包括备忘录递归和动态规划解法,帮助读者深入理解正则表达式的匹配机制。

题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

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

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

解题思路

原题。但是《剑指offer》上的答案用递归时间复杂度太高(重叠子问题很多),可以用dp实现。

  • 递归(简单写法):《剑指offer》第19题的解法是暴力递归。(能会这种方式就可以啦!)

  • 动态规划推荐看这个题解,看看别人的思考过程是怎么样的,是如何一步步推出状态转移方程的

    问:怎么知道这个问题是个动态规划问题呢?怎么看出它就存在「重叠子问题」呢? => 题解

    # 带备忘录的递归   题解是python代码,不是很友好
      def isMatch(text, pattern) -> bool:
          memo = dict() # 备忘录
          def dp(i, j):
              if (i, j) in memo: return memo[(i, j)]
              if j == len(pattern): return i == len(text)
      
              first = i < len(text) and pattern[j] in {text[i], '.'}
              
              if j <= len(pattern) - 2 and pattern[j + 1] == '*':
                  ans = dp(i, j + 2) or \
                          first and dp(i + 1, j)
              else:
                  ans = first and dp(i + 1, j + 1)
                  
              memo[(i, j)] = ans
              return ans
          
          return dp(0, 0)
      
      # 暴力递归
      def isMatch(text, pattern) -> bool:
          if not pattern: return not text
      
          first = bool(text) and pattern[0] in {text[0], '.'}
      
          if len(pattern) >= 2 and pattern[1] == '*':
              return isMatch(text, pattern[2:]) or \
                      first and isMatch(text[1:], pattern)
          else:
              return first and isMatch(text[1:], pattern[1:])
    

参考代码

暴力递归(最起码能会这种方法,看注释)

class Solution {
public:
	bool isMatch(string str, string pattern)
	{
		if (str.length() == 0 && pattern.length() == 0)
			return true;
		if (pattern.length() == 0)
			return false;

		return matchRecursively(str, pattern, 0, 0);
	}

	bool matchRecursively(string &str, string &pattern, int indexOfStr, int indexOfPattern) {
		if (indexOfStr == str.length() && indexOfPattern == pattern.length())
			return true;
		if (indexOfPattern == pattern.length())
			return false;
		
		// 当模式串中的第二个字符是*时,情况会复杂一些。(注意防止越界)
		if (indexOfPattern < pattern.size()-1 && pattern[indexOfPattern + 1] == '*') {
			if (str[indexOfStr] == pattern[indexOfPattern] || (indexOfStr != str.length() && pattern[indexOfPattern] == '.'))
				// 不管*匹配此字符几次,当前的选择只有两个:不匹配/匹配(即匹配0次/匹配1次)。所以可以这样处理
				return matchRecursively(str, pattern, indexOfStr + 1, indexOfPattern) ||  // *匹配,但是此时模式串并不向后移动字符。这样的话就包含了匹配1次和匹配多次的所有情况了。(递归思想)
				//matchRecursively(str, pattern, indexOfStr + 1, indexOfPattern + 2) ||    // 注释掉这一行 运行用时从1716ms降低到了40ms,太迷了
				matchRecursively(str, pattern, indexOfStr, indexOfPattern+2);  // *不匹配
			else
				return matchRecursively(str, pattern, indexOfStr, indexOfPattern+2);
				
		}else {
			if (str[indexOfStr] == pattern[indexOfPattern] || (indexOfStr != str.length() && pattern[indexOfPattern] == '.'))
				return matchRecursively(str, pattern, indexOfStr + 1, indexOfPattern + 1);
			else
				return false;
		}

	}

};

动态规划解法(强烈推荐,先去看44题,44题会了之后这个直接看代码就能看懂了)

class Solution {
public:
	bool isMatch(string s, string p){
		int m = s.size();
		int n = p.size();
		if(m == 0 && n == 0)
            return true;
        if(n == 0)
            return false;
        
        // 初始化
        bool dp[m + 1][n + 1] = {0};
        memset(dp, 0, sizeof(dp));  // 不加这一行就出错了,说明上面的方式并不能全部初始化数组为0。(数组长度是变量时,不建议在定义时初始化)
        dp[0][0] = true;  // 重要
        for(int j = 2; j <= n; j++)   // 这里的初始化方式类似 第44题
            dp[0][j] = dp[0][j-2] && p[j-1] == '*';
        
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(s[i-1] == p[j-1] || p[j-1] == '.'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(p[j-1] == '*'){
                	// 因为'*' 可以匹配零个或多个前面的那一个元素,所以这里还会和前面那一个元素是什么有关系,应继续分情况
                    if(s[i-1] == p[j-2] || p[j-2] == '.')  // 之前没注意到,其实这里的j-2有越界的风险,先这样。。。
                        dp[i][j] = dp[i][j-2] || dp[i-1][j];  // 不匹配/匹配
                    else
                        dp[i][j] = dp[i][j-2];
                }
            }
        }
        
        return dp[m][n];
	}

};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值