这两天翻了很多题解,思路讲解方面总是要么不够清楚要么有些许漏洞,不易于像我这样的新手理解;现在我终于弄明白解题思路了,详细整理一下,希望能让更多的朋友少走弯路。
题目解析
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
s只能包含英文字母,p只能包含英文字母和'.','*';都可以为空
保证每次'*'出现前面必定有一个有效的可以用来匹配的字母元素
(不能出现连续的`'*'`)
目标是p的所有元素经过'*'和'.'处理之后与s相同
函数原型为:
bool isMatch(const char *s, const char *p)
举几个例子:
isMatch("aa","a") → false <- "a" does not match the entire string "aa"
isMatch("aa","aa") → true <- "aa" matches the entire "aa"
isMatch("aa", "a*") → true <- "*" means matches one or more preceding chars, 'a'*2 == "aa"
isMatch("aa", ".*") → true <- '.' 等于任意字符,'.*'指matches任意字符任意次数
isMatch("aab", "c*a*b") → true <- 第一个'*'matches zero char, 第二个'*'matches "aa"
isMatch("aaaa","ab*a*c*a") → true <- 第一个'*' matches zero char, 第二个'*'matches'a', "aa", "aaa", 也可以matches "aaaa", 根据第三个'*'判断第二个'*'应该对应的是"***"
递归解法
分析
因为*
号最复杂,甚至可以对应零次,所以分成两种情况分别考虑:p[1] == '*'
和 p[1] != '*'
;
对比指对s和p的首字母进行比较:(s != "" && (s[0]==p[0] || '.'==p[0]))
p[1] != '*'
时, 说明当前的p[0]
不可以对应0到n个字符,所以直接对比就可以,剩下的交给递归- 判等:
s[0] == p[0] || p[0] == '.'
- 递归判断
isMatch(s.substr(1), p.substr(1))
两个子串 p[1] == *
时,说明当前p[0]
可以对应0到n个字符(会发生0到n次的变化,而且都是有效的- 假设只对应0个字符(假设发生0次):直接跳过
p[0]
,p[1]
,进行递归isMatch(s, p.substr(2))
- 假设对应1个以上的字符(假设发生1次以上):需要先判断是否相等,然后用
s.substr(1)
进行递归:(s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p)
代码实现
class Solution(object){
bool isMatch(string s, string p){
if (p == ""){
return s == "";
}
if (p.size() > 1 && p[1] == '*'){
// Remember: not > and > or
return isMatch(s, p.substr(2)) || (s != "" && (s[0] == p[0] || '.' == p[0])) && isMatch(s.substr(1), p);
} else {
return (s != "" && (s[0] == p[0] || '.' == p[0])) && isMatch(s.substr(1), p.substr(1));
}
}
};
DP解法
dp[i][j]
指s的前i个元素是否能被p的前j个元素匹配
思路整理
状态转移方程
已知dp[i-1][j-1]
,也就是当前面的子串都已经匹配时,需要直到下一个元素的情况(s[i]
, p[j]
),就是找出dp[i][j]
的值。
分情况讨论:
- 最简单的情况,
s[i] == p[j]
->dp[i][j] = dp[i-1][j-1]
- 从
p[j]
所有可能的值来考虑:p[j] == '.'
->dp[i][j] = dp[i-1][j-1]
p[j] == '*'
->…?(这部分真的难TT 现学动态规划、瞅了十几篇题解才弄清楚hhh)
怎么区分p[j] == '*'
的几种情况?
分析:
'*'
的作用是匹配零个或几个它前面的元素p[j-1]
。- 形如 任意字母+
'*'
的子串对匹配结果没有影响。 - s的前一个元素能匹配上它才有用,不然
'*'
也无能为力,只能让前一个字符消失,也就是匹配前一个字符零次。整个字符串能否匹配成功取决于去掉'*'
和p[j-1]
后的子串。 - 换句话来说,当
p[j] == '*'
时,dp[i][j]
只取决于dp[i][j-2]
,与dp[i][j-1]
无关。 - 如果只有
s[i] == p[j-1]
,匹配能否成功取决于dp[i-1][j-2]
,如果后者为true
,前者也一定正确;如果后者返回false
,整个字符串匹配失败。所以不需要考虑只有一个元素匹配成功的情况,直接令dp[i][j] = dp[i][j-2]
就可以了。
所以得出下面几种情况:
- 当
s[i] == p[j-1]
或者p[j-1] == '.'
时:- 能匹配零个元素:
dp[i][j] = dp[i][j-2]
- 能匹配多个元素:
dp[i][j] = dp[i-1][j]
对dp[i][j]
进行赋值时,先检查能不能匹配零个元素,再检查能不能匹配多个元素(使用||
操作符)
- 能匹配零个元素:
- 当
s[i] != p[j-1]
或者p[j-1] != '.'
时:与能匹配零个元素结果相同
代码实现
- 实际上
dp
这个二维数组的容量比s和p大一圈,因为要处理s和p都为空的情况,dp
的行标i
对应s[i]
,列标j
对应p[j]
,循环从1开始; - 初始化我用了力扣里一位深藏不露的大神的方法,只初始化
dp[0][0]
为true
,用memset()
初始化剩下的值为false
。
bool isMatch(string s, string p){
s = ' ' + s;
p = ' ' + p;
int m = s.size(), n = p.size();
bool dp[m+1][n+1];
memset(dp, false, (m+1) * (n+1));
dp[0][0] = true;
for (int i = 1; i <= m; ++i){
for (int j = 1; j <= n; ++j){
if (s[i-1] == p[j-1] || p[j-1] == '.'){
dp[i][j] = dp[i-1][j-1];
}
if (p[j-1] == '*'){
if (s[i-1] == p[j-2] || p[j-2] == '.'){
dp[i][j] = dp[i-1][j] || dp[i][j-2];
// 如果dp[i-1][j] == true,则等号左边赋值为dp[i-1][j]
} else {
dp[i][j] = dp[i][j-2];
}
} // 因为初始化为假,所以不满足为真条件时不用进行额外操作
}
}
return dp[m][n];
}
冲鸭!