剑指offer——正则表达式匹配

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

示例1:

输入:" aaa ", " a*a "

返回值:true

示例2:

输入:

输入:"aad","c*a*d"

返回值:true

说明:因为这里 c 为 0 个,a被重复一次, * 表示零个或多个a。因此可以匹配字符串 "aad"。  

示例3:

 输入:"",".*"

返回值:true

说明:".*" 表示可匹配零个或多个('*')任意字符('.')

思路及解答:

递归解答:原串定义为str,模式串为pattern。

1、如果pattern长度为0

  • str长度为0,说明刚刚好匹配完,返回true; 
  • str长度不为0,说明没有匹配完,返回false;

2、如果pattern的长度大于0

3、如果pattern的长度大于1,且第二个字符是*,说明前面的字符可以匹配0,1或者多次。分为两种情况:

  • 一种是直接把*和*前面的字符去掉,相当于匹配了0个,然后接着比较;
  • 另一种是,如果str的长度大于0,并且第一个字符匹配,那就把str的第一个字符去掉,两者接着匹配。否则,说明第二个字符不是*,那么就直接比较第一个字符是不是匹配,同时将后面的字符进行匹配。

注意:上面说的第一个字符是不是匹配,除了两个字符相等的情况,其实还有模式串的字符为'.'的情况。

Java实现代码如下:

public boolean match(String str,String pattern){
    if(pattern.length() == 0){
        return str.length() == 0;
    }
    //第二个字符是' * '
    if(pattern.length() > 1 && pattern.charAt(1) == ' * '){
        //匹配0次,直接把' * '去掉,两者判断
        return match(str , pattern.substring(2))
            //第一个字符相同的时候,去掉第一个字符,判断后面的(相当于匹配多次)
            ||(str.length() > 0 && firstSame(str,pattern)) && match(str.substring(1),pattern);
            //
    }else{
        //第二个字符不是' * '的时候,判断第一个字符是否相同,相同的时候再从第二位开始比较
        return str.length()  > 0 && firstSame(str,pattern) && (match(str.substring(1),pattern.substring(1)));
    }
}

//判断第一个字符是不是相同
private boolean firstSame(String s, String p){
    //两个相同,或者有一个是“ . ”
    return s.charAt(0) == p.charAt(0) || p.charAt(0) == '.';
}

动态规划解法:

1、首先需要定义状态,用一个二维表dp[i][j]用来表示str的前i个字符和pattern的前j个字符是否匹配。

2、需要初始化简单状态,因为后面的状态是依赖于前面的状态,因此需要初始化dp[i][j]的首行和首列。

dp[0][0] = true,表示两个空的字符串是匹配的。

dp的首列,除了dp[0][0]为true,其他都是false。因为pattern为空,但是s不为空的时候,肯定不匹配。

dp数组的首行,也就是str为空的时候,如果pattern的偶数位都是“ * ”,那么就可以匹配,因为可以选择匹配0次。

3、初始化前面之后,后面的从索引1开始匹配

  • pattern的第j个字符为" * "(即pattern[j-1] == ' * ')

如果dp[i- 1][j] = true且pattern[j-2] == ' . '的时候,则dp[i][j] = true。(表示str的前i-1个字符和pattern的前j个字符匹配,并且pattern的第j-1个是' . ',第j个是' * ',那么说明可以匹配任何字符任何次数,自然str可以多匹配一个字符。)

如果dp[i- 1][j] = true且str[i-1] == pattern[j-2],则dp[i][j] = true。(如果str的前i-1个字符和pattern的前j个字符匹配,并且str的第i个字符和pattern的第j-1个字符相等,相当于' * '前面的字符出现了1次。

如果dp[i-1][j] = true且pattern[j-2] == ' . '的时候,则dp[i][j] = true。(表示str的前 i - 1个字符和pattern的前j个字符匹配,并且pattern的前j个字符匹配,并且pattern的第j-1个字符是' . ',第j个是'  * ',那么说明可以匹配任何字符任何次数,自然str可以多匹配一个字符。)

  • pattern的第j个字符不为' * '(即pattern[j-1] != '*')

如果dp[i-1][j-1] = true and str[i-1] == pattern[j-1] 时,则dp[i][j] = true(也就是前面匹配,接下来的字符一样匹配 )

如果dp[i-1][j-1] = true 且 pattern[i-1] == '.' 时,则dp[i][j] = true(其实就是 . 可以匹配任何字符)

处理完数组之后,最后返回dp[n-1][m-1],也就是str的前n个和pattern的前m个字符是否匹配。

 Java代码实现如下:

public boolean match(String str, String pattern){
    if(pattern.length() == 0){
        return str.length() == 0;
    }
    int n = str.length() + 1;
    int m = pattern.length() + 1;
    boolean[][] dp = new boolean[n][m];
    dp[0][0] = true;
    for(int j = 2; j < m; j = j + 2){
        if(dp[0][j - 2] && pattern.charAt(j - 1) == '*'){
            dp[0][j] = true;
        }
    }
    for(int i = 1 ; i < n ; i++){
        for(int j = 1 ; j < m ; j++){
            if(pattern.charAt(j - 1) == '*'){
                dp[i][j] = dp[i][j - 2]
                    || dp[i - 1][j] && (str.charAt(i -1) == pattern.charAt(j - 2)
                    || pattern.charAt(j - 2) == '.');
            }else{
                dp[i][j] = dp[i - 1][j - 1] && (str.charAt(i -1) == pattern.charAt(j -1)
                    || pattern.charAt(j - 1) == '.');
            }
        }
    }
    return dp[n - 1][m - 1];
}

时间复杂度 O(mn) : 其中 m,n 分别为 str 和 pattern 的长度,状态转移需遍历整个 dp 矩阵。 空间复杂度 O(mn): 状态矩阵 dp 使用 O(mn)的额外空间。

你可能感兴趣的:(剑指offer刷题,正则表达式,算法,leetcode,java,数据结构,intellij-idea,动态规划)