基于动态规划实现正则表达式匹配

题目描述

给定一个字符串 s 和一个字符规律 p,实现一个支持 '.' 和 '*' 的正则表达式匹配。

  1. '.' 匹配任意单个字符
  2. '*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

  1) 0 <= s.length <= 20
  2) 0 <= p.length <= 30
  3) s 可能为空,且只包含从 a-z 的小写字母。
  4) p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
  5) 保证每次出现字符 * 时,前面都匹配到有效的字符

解题思路

用'.'和'*'来匹配字符串有很大的灵活性,因为.*可以匹配任意的字符串,这里基于动态规划来实现匹配

首先定义一个二维数组dp[p.length][s.length + 1],dp[i][j]表示p[0~i]的子字符串可以匹配s[0~ j-1]子字符串,因为.*可以匹配空字符串,因此dp[i][0]表示p[0~i]子字符串可以匹配空字符串

接下来需要找出状态之间的转移关系。

1) 首先看i=0的情况,即用p的第一个字符去匹配s,由于字符*前面一定能匹配到有效字符,故p的第一个字符只能为字母或者'.',由于单个字符是不能匹配空字符串的,故dp[0][0]=false

    接着用p的第一个字符去匹配s的其它字符,由于p[0]只能为字母或'.',当p[0]为字母时若和s的第一个字母相同,则dp[0][1]=true; 当p[0]为'.'时可以匹配任意字母,则dp[0][1]=true

    p的第一个字符只能和s的第一个字符进行匹配,故当i=0时,只需匹配第一个字符即可

2) 再来看j=0的情况,即用p去匹配空字符串,先看几个例子

     a) a*b* 可以匹配空字符串(a和b均认为出现0次)

     b) a*b 不能匹配空字符串(a可以认为出现0次,但b不能)

     c) a*bc* 不能匹配空字符串(a和c可以认为出现0次,但b不能)

     通过例子可以看出,p能匹配空字符串的必要条件时最后一个字符必须是'*', 若p[i]能匹配空字符串,且p[i+2]='*',则p[i+2]仍能匹配空字符串,因此可以得到状态转移的关系:

     若dp[i][0]=true, 且p[i+2]='*',则dp[i+2][0]=true

3) 再来看其它情况,若dp[i][j]=true,且p[i+1]=s[j+1]或者p[i+1]='.',则表明p[i+1]仍然可以匹配s[j+1],即dp[i+1][j+1]=true

4) 最后来看p[i]='*'的情况,这也是最复杂的情况,看几个例子

    a) ab可以匹配ab,则ab.*或abc*也可以匹配ab,由此可以得出转移状态: 若dp[i][j]=true,且p[i+2]='*',则dp[i+2][j]=true

    b) ab*可以匹配a,则ab*可以匹配ab、abb等,由此可以得出转移状态: 若dp[i][j]=true,且p[i]='*',若p[i-1]='.'或p[i-1]=s[j+1],则dp[i][j+1]=true

代码实现

class Solution {
    public boolean isMatch(String s, String p) {
        if (null == s || null == p) {
            return false;
        }

        if (p.equals(".*")) {
            return true;
        }
        if ("".equals(p)) {
            return "".equals(s);
        }

        //dp[i][j]表示p的0~i位字符串可以匹配s的0~j位字符串
        boolean[][] dp = new boolean[p.length()][s.length() + 1];

        for (int i=0; i < p.length(); i++) {
            for (int j=0; j < s.length() + 1; j++) {

                //p的第一个字符只需和s的第一个字符进行比较
                if (i == 0) {
                    if (!"".equals(s) && (p.charAt(0) == s.charAt(0) || p.charAt(0) == '.')) {
                        dp[0][1] = true;
                    }
                    break;
                }

                if (j == 0 && p.charAt(i) == '*') {
                    //.*在开头
                    if (i == 1) {
                        dp[i][j] = true;
                    } else if (dp[i-2][j]) {
                        //.*认为是空或匹配1个字符
                        dp[i][j] = true;
                    }
                }

                if ((j > 0 && dp[i-1][j-1]) && !"".equals(s) && 
                    (p.charAt(i) == s.charAt(j - 1) || p.charAt(i) == '.')) {
                    dp[i][j] = true;
                } else if (p.charAt(i) == '*') {
                    if (i >= 2 && dp[i-2][j]) {
                        //ab可以匹配ab,则ab.*或abc*也可以匹配ab
                        dp[i][j] = true;
                    }
                    //ab*可以匹配a,则ab*可以匹配ab、abb
                    if (j > 0 && dp[i][j-1] && (p.charAt(i-1) == '.' || p.charAt(i-1) == s.charAt(j - 1))) {
                        dp[i][j] = true;
                    }
                }
            }
        }
        return dp[p.length() - 1][s.length()];
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值