题目
// 牛客
// 请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中
// 的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现
// 任意次(含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();
}
}