剑指Offer-54-正则表达式匹配

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/82465154

项目地址:https://github.com/SpecialYy/Sword-Means-Offer

题目

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

解析

预备知识

这道题考察的是正则表达式匹配引擎的原理,只不过这里为了简单,仅仅涉及到”. or *”。
该题目如果没有理清思路,就会陷入各种复杂的情况。

思路一

首先不着急想到全部情况,而是由易到难。

情况一:假设模式串没有特殊符号

这种就是很简单的情况,直接将字符串中每一位去匹配模式串每一位即可。若某一位不相等,则说明不匹配,直接结束;否则字符串和模式串同时后移一位继续匹配下一位。

情况二:假设模式串含有’.’

由于’.’可以表示任意字符,所以在第一种情况中,若某一位不相等,则判断当前模式串是否是’.’,若是则认为可以匹配,字符串和模式串同时后移一位继续匹配下一位即可。

假设模式串含有’.’和’*’

因为’*’可以表示0或者多个它前面出现的字符,所以情况有些许复杂。但只要分类合理,也能迎刃而解。由于’*’的的特殊性,所以我们对于每一个待匹配的字符,都要判断模式串的下一位是否为’*’。

  1. 若模式串中待匹配字符j的下一个不是’*’,则直接按情况二进行匹配。
  2. 若模式串中待匹配字符j的下一个是’*’:
    1. 若字符串i与模式串j相等或者模式串j为’.’,可以有2种操作:
      1. 字符串i后移一位,模式串j位置不变,相当于模式不变,意味着此处*匹配多个字符
      2. 字符串i不变,模式串j后移2位,相当于跳过*匹配,意味着此处匹配0个字符
      3. 你可能会问如果*只匹配一个字符呢,为什么没有字符串i后移一位,模式串j后移2位情况呢,因为该情况可以看作先执行一次情况1,再执行一次情况2得到。
    2. 若字符串i与模式串j不相等,则相当于*匹配0个字符,字符串i不变,模式串j后移2位即可。

递归处理结束条件

  1. 当字符串和模式串同时走到了末尾,说明匹配成功
  2. 当字符串没有走到末尾,而模式串已经走到了末尾,则说明匹配不成功
  3. 这种就是模式串待匹配j的下一位不是’*’, 且j与字符串i的不想等且j不为’.’,说明匹配不成功。
    /**
     * 有限状态自动机做法
     * @param str
     * @param pattern
     * @return
     */
    public static boolean match2(char[] str, char[] pattern) {
        if(str == null || pattern == null) {
            return false;
        }
        return matchCore(str, 0, pattern, 0);
    }

    public static boolean matchCore(char[] str, int index1, char[] pattern, int index2) {
        if(index1 == str.length && index2 == pattern.length) {
            return true;
        }
        if(index1 != str.length && index2 == pattern.length) {
            return false;
        }
        //若待匹配的下一位为'*'
        if((index2 + 1) < pattern.length && pattern[index2 + 1] == '*') {
            if(index1 < str.length && (str[index1] == pattern[index2] || pattern[index2] == '.')) {
                return matchCore(str, index1 + 1, pattern, index2)
                        || matchCore(str, index1, pattern, index2 + 2);
            }else {
                return matchCore(str, index1, pattern, index2 + 2);
            }
        }
        //若待匹配的下一位不为'*'
        else {
            if(index1 < str.length && (str[index1] == pattern[index2] || pattern[index2] == '.')) {
                return matchCore(str, index1 + 1, pattern, index2 + 1);
            } else {
                return false;
            }
        }
    }

思路二

思路二仅仅是换种角度来理解上述代码,比如ab*ac*a模式的有限状态自动机为:
这里写图片描述
1,2,3…表示中间状态,S表示开始状态,E表示终止状态。通过这个有限状态自动机可以清晰的理解带有*的情况。

  1. 匹配a之后即可进入1状态,
  2. 之后碰到自旋转状态(因为b的下一个字符为*),所以这里可以有3种操作。第一种就是:匹配一个b,且状态不变,相当于匹配多个。第二种就是:匹配一个b,且状态变为2,相当于匹配一个。第三种:不匹配字符,直接进入状态2。
  3. 状态2匹配一个a进入状态3
  4. 状态3又是一个自旋转状态,跟状态2类似,这里不再累述。
  5. 状态4匹配一个a进入终止状态,即匹配完毕

以上就是以有限状态自动机的角度来进行匹配,可以说是非常好理解的机制了。

总结

只要能够合理对各种情况进行分类,就能写出清晰的代码。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/82465154