Leetcode刷题-10:正则表达式匹配

解题思路

    • 1 题目描述:
    • 2 题目分析:
      • 2.1 简单尝试
      • 2.2 状态转移方程
      • 2.3 具体思路
    • 3 题目解答

1 题目描述:

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

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

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

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

示例2:

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

来源:力扣(LeetCode)

2 题目分析:

2.1 简单尝试

看到题目首先想到的是利用从左到右分别遍历两个字符串的的方法来进行分情况讨论,具体有以下思路:
分别以i和j作为字符串s和p的移动下标

  1. 如果s[i] == p[j],则继续进行下一个字符的扫描
  2. 如果s[i] != p[j],此时就要分情况讨论。如果是p[j] = ‘*’,转到第3步,如果p[j]==’.’,转到第4步,否则匹配失败。
  3. 此时的*可能匹配0个、1个或者多个前面的字符,针对这三种情况分别考虑以下情况

// 多个字符匹配的情况
s[i]是否等于p[j-2] // 单个字符匹配的情况
s[i]是否等于p[j] // 没有匹配的情况

  • 当作匹配成功继续下一步
    可以看到在p[j]为✳的时候对于各种情况的决定是比较复杂的,但是我们也在推理过程看到s的前i个字符和p的前j个字符是否匹配可以利用其前面字符匹配的结果来判定。着我们就很容易迁移到状态转移方程来设定求解过程。

2.2 状态转移方程

  • 首先定义状态,我们用dp[i][j] 表示 s 的前 i 个字符是否能被 p 的前 j个匹配(注意不要搞混了);怎么确定转移方程呢?首先求dp[i][j]从已经求出了的 dp[i-1][j-1] 等值来入手,再加上已知的 s[i]p[j],来决定如何求出 dp[i][j]。这里面说的已知 dp[i-1][j-1] 意思就是前面子串匹配结果都已经匹配上了,不知道新的一位的情况。那么我们就可以分情况考虑了,这是因为对于新的一位 p[j]和s[i] 的值不同,要分情况讨论:
  • 首先考虑最简单的 p[j] == s[i],此时 已经确定了当前的两个值是相等的,所以只需要判断前面的判定结果是否为真,所以就有: dp[i][j] = dp[i-1][j-1]
  • 然后从 p[j] 其他可能的情况来考虑,让 p[j]=各种能等于的东西,这其中就包括'·'和’✳‘
  • 如果p[j] == '.'的话那么就十分简单了还是相当于 p[j] == s[i],所以就有: dp[i][j] = dp[i-1][j-1]
  • 至于p[j]为✳就比较复杂了,这其中就要区分前面提到的✳分别匹配0个和多个字符。
    • 首先,考虑匹配0个字符,的作用中的让前一个字符消失,也就是匹配 0次前一个字符。此时有dp[i][j] = dp[i][j-2]。比如说(se, sec* )的匹配就是看(se,se)的匹配。

    • 其次,考虑匹配多个字符,也就是在匹配1,2,3,⋯ 次的情况下,此时会有下式成立

      当s[i] == p[j-1],有dp[i][j]=dp[i−1][j−2]
      当s[i] == s[i-1] == p[j-2],有dp[i][j]=dp[i−2][j−2],
      当s[i-2] == s[i] == s[i-1] == p[j-3],有dp[i][j]=dp[i−3][j−2],

    • 但是回头想想这样一次次列举是十分麻烦的,可以利用其规律进行归纳,然后我们就得出当p[j]是时只有下面两种情况,

      匹配 s 末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
      不匹配字符,将该组合扔掉,不再进行匹配。

    • 这样我们就得出以下的状态转移方程

        > 当s[i] != p[j-1],`dp[i][j]=dp[i][j-2]`-->`✳`表示匹配0次前面一个字符,也就是扔掉a*
        > 当s[i] == p[j-1],`dp[i][j]=dp[i-1][j] || dp[i][j-2]`-->✳`表示匹配1次前面的字符a,将此a排除并继续进行匹配
      

2.3 具体思路

这样我们就得出了总体的思路:如下归纳:
如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
如果 p.charAt(j) == ‘.’ : dp[i][j] = dp[i-1][j-1];
如果 p.charAt(j) == ‘*’:

  1. 如果 p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //a* 被删除
  2. 如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == ‘.’:
  • dp[i][j] = dp[i-1][j] //此时a*看作多个a
  • dp[i][j] = dp[i][j-1] // 此时a*看作一个a
  • dp[i][j] = dp[i][j-2] //此时a*看作多个空

3 题目解答

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();
        boolean[][] dp = new boolean[m+1][n+1];
        dp[0][0] = true;
        for(int i=0;i<=m;i++){
            for(int j = 1;j<=n;j++){
                if(p.charAt(j-1) == '*'){
                    dp[i][j] = dp[i][j-2];
                    if(matches(s,p,i,j-1)){
                        dp[i][j] = dp[i][j] ||dp[i-1][j];
                    }
                }else{
                    if(matches(s,p,i,j))
                        dp[i][j] = dp[i-1][j-1];
                }
            }
        }
        return dp[m][n];
    }

    public boolean matches(String s, String p, int i, int j) {
        if(i==0){
            return false;
        }
        if(p.charAt(j-1)=='.'){
            return true;
        }
        return s.charAt(i - 1) == p.charAt(j - 1);
    }
}

总结:字符串匹配这种前后关联极大的一般都可以采用递推思想来进行动态规划

你可能感兴趣的:(leetcode刷题记录,leetcode)