【JZ-19】正则表达式匹配(动态规划)

题目

【JZ-19】正则表达式匹配(动态规划)
【JZ-19】正则表达式匹配(动态规划)

方法一-动态规划

参考

思路:

假设主串 s s s 长度为 n n n,模式串 p p p 长度为 m m m,考虑模式串 p p p 的最后一个字符

  1. p p p 的最后一个字符是 a-z 的小写字母,则看该字符是否能和主串中最后一个字符匹配,也就是看 s [ n − 1 ] s[n-1] s[n−1] 和 p [ m − 1 ] p[m-1] p[m−1] 是否相等,相等的话就继续往前看 s 0... n − 2 s_{0...n-2} s0...n−2​ 和 p 0... m − 2 p_{0...m-2} p0...m−2​ ,不等的话直接返回 f a l s e false false
  2. p p p 的最后一个字符是".",能与任意字符匹配,所以继续往前看 s 0... n − 2 s_{0...n-2} s0...n−2​ 和 p 0... m − 2 p_{0...m-2} p0...m−2​
  3. p p p 的最后一个字符是"*",表示 p [ m − 2 ] = c p[m-2]=c p[m−2]=c 可以重复 0 次或多次,这时可以将 p [ m − 2 ] p[m-2] p[m−2] 和 p [ m − 1 ] p[m-1] p[m−1] 看作一个整体 c ∗ c* c∗:
    若 s [ n − 1 ] s[n-1] s[n−1] 不是 c,则看 s 0... n − 1 s_{0...n-1} s0...n−1​ 和 p 0... m − 3 p_{0...m-3} p0...m−3​是否匹配;
    若 s [ n − 1 ] s[n-1] s[n−1] 是 c 或者 c = ′ . ′ c=' .' c=′.′,则主串继续向前看,考虑 s 0... n − 2 s_{0...n-2} s0...n−2​ 和 p 0... m − 1 p_{0...m-1} p0...m−1​是否匹配

转移方程:

用 f [ i ] [ j ] f[i][j] f[i][j] 表示 s s s 的前 i 个字符与 p p p 的前 j 个字符能否匹配。

  • 前两种情况可以合并为: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] f[i][j]=f[i-1][j-1] f[i][j]=f[i−1][j−1]
  • 第三种情况可以分成看 c ∗ c* c∗ 和不看 c ∗ c* c∗ 两种情况:
    不看 c ∗ c* c∗ (即 s [ n − 1 ] s[n-1] s[n−1] 不是 c): f [ i ] [ j ] = f [ i ] [ j − 2 ] f[i][j]=f[i][j-2] f[i][j]=f[i][j−2]
    看 c ∗ c* c∗(即 s [ n − 1 ] s[n-1] s[n−1] 是 c 或者 c = ′ . ′ c=' .' c=′.′) : f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j]

初始条件:

  • 主串和模式串均为空串:直接返回 t r u e true true
  • 模式串为空,主串非空:直接返回 f a l s e false false
  • 其他情况需要计算

代码:

class Solution {
    public boolean isMatch(String s, String p) {
        int n = s.length();
        int m = p.length();
        boolean[][] f = new boolean[n + 1][m + 1];
        for(int i = 0; i <= n; i++){
            for(int j = 0; j <= m; j++){
                //若模式串为空,主串也为空匹配成功,主串非空匹配失败
                if(j == 0){
                    f[i][j] = (i == 0) ? true : false;
                }else{//模式串非空,分别考虑*和非*两种情况下的转移
                    if(p.charAt(j - 1) != '*'){
                        if(i > 0 && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.')){
                            f[i][j] = f[i - 1][j - 1];
                        }
                    }else{//*情况下,分别考虑看c*和不看c*两种情况
                        //不看c*
                        if(j >= 2){
                            f[i][j] = f[i][j - 2];
                        }
                        //看c*
                        if(i >= 1 && j >= 2 && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.')){
                            f[i][j] |= f[i - 1][j];
                        }
                    }
                }
            }
        }
        return f[n][m];
    }
}

notes:先算的是不看星号的情况,再算看星号的情况,即,对于 f [ i ] [ j ] f[i][j] f[i][j] 我们计算了两次,如果在不看星号时已经得到了 t r u e true true 的结果,那么无论在看星号时得到的结果是什么, f [ i ] [ j ] f[i][j] f[i][j] 都应该保持 t r u e true true 不变。也就是说,只要两次计算中有一种情况能匹配,结果就是 t r u e true true ,所以用的是 ∣ = |= ∣= 而非 = = =

复杂度:

  • 时间复杂度: O ( m n ) O(mn) O(mn),需要计算出所有 f [ i ] [ j ] f[i][j] f[i][j] 的值,每个状态在进行转移时时间开销为 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( m n ) O(mn) O(mn),即存储所有 f [ i ] [ j ] f[i][j] f[i][j] 所占用的空间
上一篇:KMP算法,你想知道的都在这里!(算法优化篇)


下一篇:力扣第23题 有效的括号