《剑指Offer》Java刷题 NO.52 正则表达式匹配(字符串、正则表达式、递归、动态规划)

《剑指Offer》Java刷题 NO.52 正则表达式匹配(字符串、正则表达式、递归、动态规划)

传送门:《剑指Offer刷题总目录》

时间:2020-07-01
题目:

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


思路:相当于实现new String(str).matches(new String(pattern));
方法一:递归

".*"能和任意长度的任意字符匹配,Java要随时检查是否越界
假设主串为s,长度为sn, 模式串为p,长度为pn,设置两个指针,sx和px,一开始sx=px,因为'*'前面的字符可以出现任意次,索引我们先按p[px+1]是否为'*'来分类
1. p[px+1]!=’*’:

  • 如果p[px]为正常字符, 那么我们看是s[sx]是否等于p[px], 如果相等,说明当前位匹配成功,接下来sx+1,px+1,如果不相等直接返回false
  • 如果p[px] 为'.', 它能匹配任意字符,接下来sx+1,px+1
    2. p[px+1]==’*’:有两种情况
  • 如果K在s中重复0次【就算当前p[px]==s[sx]或者p[px]=='.'也可以看成重复0次(eg:"aa","aa*a")】,则pattern数组直接跳过两个,sx不变,px+2
  • 如果p[px]重复一次或者多次,也就是至少一次,那么条件是p[px]==s[sx]或者p[px]=='.',然后一个一个去匹配,sx+1,px不变
  • 最后返回以上两种情况的“或”
    递归终止条件:
  • 两方同时结束:返回true;
  • pattern先到尾:返回false
  • string先到尾:继续,因为pattern可能剩下一些x*
    方法二:动态规划
    boolean数组match[sx][px]表示长度为sx的s的子串和长度为px的p的子串是否成功匹配,对应当前s和p的元素为:s[sx-1]和p[px-1]
    分情况讨论:
    1. p[px-1]!=’*’:
  • p[px-1]==s[sx-1]或者p[px-1]=='.',说明当前位匹配成功,那么match[sx][px]=match[sx-1][px-1],如果不相等直接match[sx][px]=false【boolean数组默认初始化为false】
    2. p[px-1]==’*’:
  • p[px-2]==s[sx-1]或者p[px-2]=='.',那么s[sx-1]可能会重复0次【eg:“aa”,"aaa"直接把"x"砍掉,取决于match[sx][px- 2]】或者多次(>=1)【取决于match[sx-1][px]】,match[sx][px]=match[sx][px-2] || match[sx-1][px]
  • 否则,直接把"x*"砍掉:match[sx][px]=match[sx][px-2]
    数组初始化:
  • s的空字符串可以匹配p的空字符串:match[0][0]=true;
  • s的空字符串还可以匹配"abc*…":所以应该把match[0][x](对应p[x-1])这样初始化:
  1. p[x-1]!="*",match[0][x]=false【默认值】
  2. p[x-1]=="*",match[0][x]=match[0][x-2]

Java代码

/**
 * @author LiMin
 * @Title: Match
 * @Description: 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。
 * 在本题中,匹配是指字符串的所有字符匹配整个模式。
 * @date 2020/7/2  21:12
 */

public class Match {
    public static void main(String[] args) {
        char[] string = " ".toCharArray();
        char[] pat = " ".toCharArray();
        Match m = new Match();
        System.out.println(m.matchTwo("aa".toCharArray(), "aa*a*a".toCharArray()));
    }

    /**
     * 递归法
     */
    public boolean matchOne(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        int sx = 0;
        int px = 0;
        return matchhelper(str, sx, pattern, px);
    }

    public boolean matchhelper(char[] str, int sx, char[] pattern, int px) {
        //先列出递归终止条件
        //同时结束
        if (sx == str.length && px == pattern.length) {
            return true;
        }
        //pattern先结束
        if (sx != str.length && px == pattern.length) {
            return false;
        }

        //现在只剩下px<pattern.length的情况
        if (px + 1 < pattern.length && pattern[px + 1] == '*') {//pattern有下一个符号,且下一个符号为*
            if (sx != str.length && (str[sx] == pattern[px] || pattern[px] == '.')) {
                return matchhelper(str, sx, pattern, px + 2)//匹配0次,pattern直接跳过两个字符,即"x*"
                        || matchhelper(str, sx + 1, pattern, px);//匹配1次或多次,一个一个往后匹配
            } else {
                return matchhelper(str, sx, pattern, px + 2);//匹配0次,pattern直接跳过两个字符,即"x*"
            }
        } else {//pattern有下一个符号,且下一个符号不为*或者pattern没有下一个符号,即px==pattern.length-1
            if (sx != str.length && (str[sx] == pattern[px] || pattern[px] == '.')) {
                return matchhelper(str, sx + 1, pattern, px + 1);
            } else {
                return false;
            }
        }
    }

    /**
     * 动态规划
     */
    public boolean matchTwo(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        boolean[][] match = new boolean[str.length + 1][pattern.length + 1];//boolean默认初始化为false
        match[0][0] = true;//空字符串和空字符串相匹配,还和"a*b*c*.."类型的相匹配
        for (int i = 1; i <= pattern.length; i++) {
            if (pattern[i - 1] == '*') {
                match[0][i] = match[0][i - 2];
            }
        }
        //match[sx][px]对应当前s和p的元素为:s[sx-1]和p[px-1]
        for (int sx = 1; sx <= str.length; sx++) {
            for (int px = 1; px <= pattern.length; px++) {
                if (pattern[px - 1] == '.' || str[sx - 1] == pattern[px - 1]) {//当前元素相匹配
                    match[sx][px] = match[sx - 1][px - 1];
                }//str[sx-1]!=pattern[px-1]时默认match[sx][px]为false
                else if (pattern[px - 1] == '*') {
                    if (pattern[px - 2] == '.' || str[sx - 1] == pattern[px - 2]) {//可能重复0次或者多次
                        match[sx][px] = match[sx][px - 2] || match[sx - 1][px];
                    } else {
                        match[sx][px] = match[sx][px - 2];
                    }
                }
            }
        }
        return match[str.length][pattern.length];
    }
}
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页