面试算法之正则表达式匹配

题目:

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

思路

假设 字符串为 str , 正则表达式为 exp ,显然这里的变量是 exp,而表达式中最让人头疼的部分就是碰到了 * 怎么办?
所以可以根据表达式中 * 的出现情况分类讨论,这里采用递归实现的办法,更优化的方法应该是用动态规划,但动态规划有些地方并不是很懂,后续有机会在补上动态规划版本。

递归思路

情况一:

首先考虑最后的情况,也就是当 exp 已经来到了末尾位置(为了方便讨论,这里的末尾位置指的是最后一个字符后面的位置,并不是指最后一个字符位置,注意区分,下同),此时如果 str 没有来到末尾位置,那么他们一定匹配不上,而如果之前都匹配成功,此时 exp 和 str 又同时来到末尾,那最后一定是匹配成功了。

现在考虑末尾之前的情况。

情况二:
如果此时 exp 来到了 j 位置, 而且 j + 1 就是末尾位置,

  1. 首先 str 不能来到末尾位置,因为如果 str 来到末尾位置,那就没有字符可以和 j 位置匹配,而 j + 1 的下一位置是末尾,不是 * ,也不可能通过 * 消去 j 位置的字符,所以只要 str 来到了 末尾位置,一定匹配不上。
  2. 如果 str 不在末尾位置,说明 str 中还有字符和 j 位置作匹配,假设 str 此时的位置为 i 位置, 那么 i 和 j 匹配成功的可能性有两种,要么 两个位置的字符是同一个,要么 j 位置的字符是 '. ',它可以表示任意字符,因此也可以和 i 匹配成功。不满足任意一个,都会匹配失败。
  3. 如果 i 和 j 匹配成功了,我们还要接着判断 i + 1 和 j + 1 的位置能否继续匹配成功,此时进入递归。

情况三:

exp 来到了 j 位置, 而且 j + 1 的位置不是 * ,此时的判断逻辑和情况二完全相同,因为 只要 j + 1 的字符不是 * ,那么 首先str 不能来到 末尾位置,并且 i 位置 和 j 位置要能匹配成功,同时还要接着匹配 i + 1 和 j + 1 的情况。

情况四:

只有不满足上述三种情况时,才能走到情况四这一步,这表明了 当前 exp 的位置 j 不在末尾,并且 下一个字符是 * ,那就要依据 str 此时的情况来分析。因为 * 可以表示任意数量的前一个字符,所以在 str 位置 i 及 i 后面可以出现重复的相同字符。

  1. 最好的一种情况,i 位置 和 j 位置字符不匹配,不匹配没关系,* 可以直接消除 j 位置当前这个不匹配的字符,接着 判断 i 和 j + 2 (j + 1 是 * ,不用再判断)的字符是否匹配,进入递归。
  2. 而如果 i 和 j 匹配,因为此时 str 可能出现重复字符,所以要挨个判断。只要当前 i 还没有来到 str 末尾,并且 i 位置 和 j 位置一直能匹配上,i 一直 + 1,同时要依次判断每个 i 位置 和 j + 2 的位置之后的匹配情况,因为我们并不清楚 j + 1 位置的 * 到底应该表示多少个 j 位置 字符后面才能匹配成功, 所以只能依次去判断假如当前 i 和 j + 2 匹配,以后还能不能成功继续匹配 ,如果其中有一次匹配成功了,直接就可以返回 True,否则 还需要判断 i + 1和 j + 2 的 情况。这个判断要一直进行下去,直到 当前的 i 和 j 位置匹配不上了,说明刚进入循环判断时的那个 i 位置 的重复字符已经结束了,现在的 i 来到了 不重复的 位置上。

代码

def process(str, exp, i, j):

    if j == len(exp):
        return i == len(str)  # 如果exp已经到尾了,而str还没有结束,一定匹配不上

    # 如果exp在最后一个字符,或者exp 的下一个字符不是*,那么
    # 当 str 到尾,肯定匹配不上;str没有到尾的前提下,如果当前字符位置字符相等,或者exp位置为'.',两者当前位置才能
    # 匹配成功,进而递归下一个位置是否匹配成功
    if j + 1 == len(exp) or exp[j + 1] != '*':
        return i != len(str) and (exp[j] == str[i] or exp[j] == '.') and process(str, exp, i + 1, j + 1)

    # 当 exp的下一个字符是 * ,并且 j 不是最后一个位置,当 str 没有到尾,只要当前位置的字符匹配,或者exp位置是'.',
    # 判断 j + 2 的位置和i+1是否匹配。因为j +1是*,可以表示任意数量的前一个字符,所以如果i+1和j+2匹配上,那么*就可以表
    # 示为1个,如果匹配不上,继续判断下一个i位置,整个过程必须保持j位置的字符和i位置的字符能匹配,也就是说i位置一直更新
    # 到和进入while循环时的字符不一致,也就是不再有重复字符为止。
    while (i != len(str) and (exp[j] == str[i] or exp[j] == '.')):
        if process(str, exp, i, j + 2):
            return True
        i += 1
    # 当 exp 和 str的当前字符匹配不上(字符不相等或者不是'.')时,由于当前 j +1位置是*,可以看做当前位置是0个,直接消掉,
    # 就可以再继续判断*后面的字符和i是否匹配
    return process(str, exp, i, j + 2)


def isMatch(str, exp):
    if len(str) == 0 and len(exp) == 0:
        return True

    return process(str, exp, 0, 0)

猜你喜欢

转载自blog.csdn.net/yaogepila/article/details/106601190
今日推荐