【剑指offer】19. 正则表达式匹配

167 阅读7分钟

题目

在这里插入图片描述 在这里插入图片描述

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

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

题解

/////////////////////////////// 递归法 //////////////////////////////

// 牛客
// 运行时间:13ms
// 占用内存:9728k
public class Solution {
	public boolean match(char[] str, char[] pattern) {
		if (str == null || pattern == null)
			return false;
		int s = 0;  // 初始化str的遍历指针s
		int p = 0;  // 初始化pattern的遍历指针p
		// 匹配函数,匹配成功返回true
		return matchFunc(str, s, pattern, p);
	}

	public boolean matchFunc(char[] str, int s, char[] pattern, int p) {
		// 如果一起到达str和pattern的尾部,匹配成功,返回true
		if (s == str.length && p == pattern.length) 
			return true;
		// 如果pattern遍历完了(匹配符用完了)str没遍历完,返回false
		if (s != str.length && p == pattern.length) 
			return false;
			
		// 如果遍历指针p的下一个是'*'(根据'*'的定义,默认'*'不可能在第一个出现)
		// 且p不超过遍历范围。那么有'*'的情况是最复杂的,这个题目也是主要解决这个问题。
		if (p + 1 < pattern.length && pattern[p + 1] == '*') {
			// 如果p和s遍历字符相匹配(不管是字符相同的匹配还是有万能符'.'的匹配)
			// 此时有三种情况
			if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
				// 第一种情况是:p当前遍历匹配了0个字符(即使p和s当前遍历字符相匹配,pattern[p]
				// 和后面的'*'也有可能代表匹配了0个字符,而把str[s]交给后面字符来匹配)
				// 第二种情况是:pattern[p]和'*'匹配了str的一个字符,此时s右移一次,
				// p右移两次,跨过'*'。
				// 第三种情况是:pattern[p]和'*'匹配了str的两个字符(或以上,但这个“以上”要留给下一个递归来判断)
				// 所以此时s右移一次,p不动
				// 取三种递归的逻辑与,只要其中一种情况是true就行。
				return matchFunc(str, s, pattern, p + 2) 
					|| matchFunc(str, s + 1, pattern, p + 2)
					|| matchFunc(str, s + 1, pattern, p);
			}
			// 如果p和s遍历字符不匹配,由于'*'可以代表前面字符出现0次
			// 所以不能直接返回false,还要继续往下判断。因此p右移两格继续判断
			// 所以这里跟if中返回的matchFunc(str, s, pattern, p + 2)是一样的
			else {
				return matchFunc(str, s, pattern, p + 2);
			}
		}
		// 如果遍历指针p的下一个字符不是'*'
		else {
			// 若满足:(s没超过遍历范围,且s遍历值等于p遍历值),或者
			// 满足:(s没超过遍历范围,且p遍历值此时是万能匹配符'.')
			// 所以这里跟下一个是'*'情况中的第一个if是一样的。
			if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
				// 递归调用matchFunc,s p指针一起右移一位。
				return matchFunc(str, s + 1, pattern, p + 1);
			}
		}
		return false;  // 其他情况返回false
	}
}


// 力扣
// 执行用时:879 ms, 在所有 Java 提交中击败了5.04%的用户
// 内存消耗:36.8 MB, 在所有 Java 提交中击败了96.62%的用户
class Solution {
    public boolean isMatch(String string_s, String string_p) {
        if (string_s == null || string_p == null)
            return false;
        char[] str = string_s.toCharArray();
        char[] pattern = string_p.toCharArray();
        int s = 0;
        int p = 0;
        return matchFunc(str, s, pattern, p);
    }
    
    public boolean matchFunc(char[] str, int s, char[] pattern, int p) {
        if (s == str.length && p == pattern.length)
            return true;
        if (s < str.length && p == pattern.length)
            return false;
        // 如果pattern下一个是'*'
        if (p + 1 < pattern.length && pattern[p + 1] == '*') {
            // 且当前p和s遍历值匹配(不管是字符匹配还是万能符'.'匹配)
            if ((s < str.length && str[s] == pattern[p]) || (s < str.length && pattern[p] == '.')) {
                return matchFunc(str, s, pattern, p + 2)
                    || matchFunc(str, s + 1, pattern, p + 2)
                    || matchFunc(str, s + 1, pattern, p);
            }
            else {
                return matchFunc(str, s, pattern, p + 2);
            }
        }
        else {
            if ((s < str.length && str[s] == pattern[p]) || s < str.length && pattern[p] == '.')
                return matchFunc(str, s + 1, pattern, p + 1);
        }
        return false;
    }
}


/////////////////////////////// 动态规划 //////////////////////////////
// 在斐波那契数列问题中,动态规划主要在一维数组中进行
// 数组元素中后一个元素状态,主要取决于前一个元素或前一些元素的状态
// 本题的矩阵形式动态规划,产生的主要原因是由于需要比对的对象不只有一个
// 而是有了两个(甚至两个以上),即需要确定的元素的状态,不仅取决于当前
// 数组A的元素状态,还取决于另外一个数组B的元素状态。
// 因此将一个数组A中的元素作为横轴,另一个数组B中元素作为数轴,就需要在矩阵形式dp。

// 假设str长度为slen,pattern长度为plen,i表示str的遍历索引,
// j表示pattern的遍历索引,题目的子问题其实就是看str[:i]和pattern[:j]是否匹配,
// 而dp[i+1][j+1]就是这个意思,假设str表示纵轴,pattern表示横轴
// dp[i+1][j+1]==true则为匹配,也就 表示遍历str索引为i的元素,
// 与遍历pattern索引为j的元素是匹配的。
// 而dp[0][0]初始化为true,是表示str和pattern为空时也能匹配。


// 牛客
// 运行时间:11ms
// 占用内存:9680k
public class Solution {
	public boolean match(char[] str, char[] pattern) {
		int slen = str.length;
		int plen = pattern.length;
		// 定义dp动态规划矩阵,dp[i][j]表示的是p的前j个字符和s的前i个字符匹配的结果
		boolean[][] dp = new boolean[slen + 1][plen + 1];
		
		dp[0][0] = true;  // dp[0][0]初始化为true
		// 初始化第一行,首行str为空字符串
		// 因此当pattern的偶数位为'*'时才能够匹配,这时'*'看做出现0次
		for (int i = 1; i <= plen; i++) {
			// 默认pattern第一个元素不会是'*',所以不用考虑超出边界
			if (pattern[i - 1] == '*')
				dp[0][i] = dp[0][i - 2]; 
		}
		// 从第1行第1列开始,从左到右,从上到下遍历矩阵每个位置
		// 需要注意,遍历位置索引分别为i-1和j-1(而不是i j)
		for (int i = 1; i <= slen; i++) {  // 遍历str,索引记为i
			for (int j = 1; j <= plen; j++) {  // 遍历pattern,索引记为j
				// 若遍历位置i-1和j-1的元素相匹配(不管是字符匹配还是万能符'.'匹配)
				if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') {
					dp[i][j] = dp[i - 1][j - 1];  // i和j都直接往下遍历一位
				}
				// 若j-1遍历元素为'*'
				else if (pattern[j - 1] == '*') {
					// 则如果j-2(j-1的上一个)元素与当前i-1元素匹配(不管
					// 是字符匹配还是万能符'.'匹配)
					if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') {
						// A |= B为 A = A || B, 则下一个状态dp[i][j]为dp[i][j - 1],
						// dp[i - 1][j]和dp[i][j - 2]这三个状态的逻辑与
						dp[i][j] |= dp[i][j - 1];  // 表示匹配1个
						dp[i][j] |= dp[i - 1][j];  // 表示匹配2个
						dp[i][j] |= dp[i][j - 2];  // 表示匹配0个
					}
					else {  // 如果j-2元素不匹配,则下一状态等于匹配0个的情况
						dp[i][j] = dp[i][j - 2];
					}
				}
			}
		}
		return dp[slen][plen];  // 最后返回最右下角状态
	}

}


// 力扣
class Solution {
    public boolean isMatch(String string_s, String string_p) {
        if (string_s == null || string_p == null)
            return false;
        char[] str = string_s.toCharArray();
        char[] pattern = string_p.toCharArray();
		int slen = str.length;
		int plen = pattern.length;
		// 定义dp动态规划矩阵,dp[i][j]表示的是p的前j个字符和s的前i个字符匹配的结果
		boolean[][] dp = new boolean[slen + 1][plen + 1];
		
		dp[0][0] = true;  // dp[0][0]初始化为true
		for (int i = 1; i <= plen; i++) { 
			if (pattern[i - 1] == '*') 
				dp[0][i] = dp[0][i - 2]; 
		}
		for (int i = 1; i <= slen; i++) {
			for (int j = 1; j <= plen; j++) {
				// System.out.println(dpToString(dp));  
				if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') {
					dp[i][j] = dp[i - 1][j - 1];
				}
				else if (pattern[j - 1] == '*') {
					if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') {
						dp[i][j] |= dp[i][j - 1];
						dp[i][j] |= dp[i - 1][j];
						dp[i][j] |= dp[i][j - 2];
					}
					else {
						dp[i][j] = dp[i][j - 2];
					}
				}
			}
		}
		return dp[slen][plen];
	}
	
	// 打印dpToString的toString函数(不是必须)
	public String dpToString(boolean[][] dp) {
		StringBuilder res = new StringBuilder();
		for (int i = 0; i < dp.length; i++) {
			res.append("[");
			for (int j = 0; j < dp[0].length; j++) {
				if (dp[i][j])
					res.append(" true ");
				else 
					res.append(" false ");
			}
			res.append("]");
			res.append("\r\n");
		}
		return res.toString();
	}
}