正好在力扣看到两个比较相似的问题
两道题目都是关于实现通配符或者类似正则的算法,他们都可以使用动态规划的算法来求解。
题目的主要难度在与状态转移方程会有很多种情况,而且在匹配*时还可以通过保持*的匹配位置不变,移动字符串匹配的位置来实现
44. 通配符匹配
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
'?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。 两个字符串完全匹配才算匹配成功。
说明:
s 可能为空,且只包含从 a-z 的小写字母。 p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 。*
示例 1:
输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入: s = "aa" p = "" 输出: true 解释: '' 可以匹配任意字符串。
示例 3:
输入: s = "cb" p = "?a" 输出: false 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
示例 4:
输入: s = "adceb" p = "ab" 输出: true 解释: 第一个 '' 可以匹配空字符串, 第二个 '' 可以匹配字符串 "dce".
示例 5:
输入: s = "acdcb" p = "a*c?b" 输出: false
解法:动态规划
设 dp[i][j]代表 s[:i]和p[:j](包含s[i]和p[j])的匹配情况 ,于是分为以下三种情况
-
当
p[j] != '*'此时的
dp[i][j]取决于s[i]和p[j]是否能匹配(相等s[i] == p[j]或者p[j] == '?')- 如果相等则
dp[i][j] = dp[i-1][j-1] - 不相等则
dp[i][j]= false
- 如果相等则
-
当
p[j] == '*'如果
*匹配的是空字符串,则dp[i][j] = dp[i][j-1]如果
*参与了匹配,则dp[i][j] = dp[i-1][j]可以理解成·s[i]·已经符合条件,进行了剔除,所以继续拿s[i-1]和*匹配所以总的
dp[i][j] = dp[i][j-1] || dp[i-1][j]
关于边界
我们可以改变dp[i][j]成:dp[i][j]代表 s[:i]和p[:j](不包含s[i]和p[j])能否匹配,dp[0][0]就代表两个字符串都为空的情况,于是对于所有s,p的判断都变成了s[i-1], p[j-1]
关于初始值
dp[0][0]=true s 和模式 p 均为空时,匹配成功;
dp[i][0]=false,即空模式无法匹配非空字符串;
dp[0][j] 需要分情况讨论:因为星号才能匹配空字符串,
所以只有当模式 p 的前j 个字符均为星号时,dp[0][j] 才为真。
代码
class Solution
{
public:
bool isMatch(string s, string p)
{
int len_s = s.size(), len_p = p.size();
vector<vector<bool>> dp(len_s + 1, vector<bool>(len_p + 1, false));
// dp[0][0]=true s 和模式 p 均为空时,匹配成功;
// dp[i][0]=false,即空模式无法匹配非空字符串;
// dp[0][j] 需要分情况讨论:因为星号才能匹配空字符串,
// 所以只有当模式 p 的前 j 个字符均为星号时,dp[0][j] 才为真。
dp[0][0] = true;
for (int j = 0; j < len_p; j++)
{
if (p[j] == '*')
{
dp[0][j + 1] = true;
}
else
{
break;
}
}
auto match = [s, p](int i, int j) -> bool {
if (p[j] == '?')
{
return true;
}
return p[j] == s[i];
};
for (int i = 1; i <= len_s; i++)
{
for (int j = 1; j <= len_p; j++)
{
if (p[j - 1] != '*')
{
dp[i][j] = match(i - 1, j - 1) ? dp[i - 1][j - 1] : false;
}
else
{
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
}
}
}
return dp[len_s][len_p];
}
};
10. 正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。 p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1:
输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入: s = "aa" p = "a*" 输出: true 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入: s = "ab" p = "." 输出: true 解释: "." 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入: s = "aab" p = "cab" 输出: true 解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入: s = "mississippi" p = "misisp*." 输出: false
解法:动态规划
这道题和上题的唯一区别是,此题要求的*是和前一个字符相关。所以整体的思路差不多
设 dp[i][j]代表 s[:i]和p[:j](不包含s[i]和p[j])的匹配情况 ,于是分为以下四种情况
-
当
p[j] != '*'此时的
dp[i][j]取决于s[i]和p[j]是否能匹配(相等s[i] == p[j]或者p[j] == '.')- 如果相等则
dp[i][j] = dp[i-1][j-1] - 不相等则
dp[i][j]= false
- 如果相等则
-
当
p[j] == '*'如果
*匹配的是空字符串,则dp[i][j] = dp[i][j-2],注意此时是j-2因为空字符意味着***和它前面的字符**都不参与如果
*参与了匹配,则dp[i][j] = dp[i-1][j]可以理解成s[i]已经符合条件,进行了剔除,所以继续拿s[i-1]和*匹配所以总的
dp[i][j] = dp[i][j-2] || dp[i-1][j]
关于初始值
dp[0][0]=true s 和模式 p 均为空时,匹配成功;
dp[i][0]=false,即空模式无法匹配非空字符串;
dp[0][j] 根据实际计算了;
代码
class Solution {
public:
bool isMatch(string s, string p) {
int x = s.size();
int y = p.size();
vector<vector<bool>> dp(x+1, vector<bool>(y+1,false));
dp[0][0] = true;
auto m = [&](int i, int j) {
if (i == 0) {
return false;
}
if (p[j - 1] == '.') {
return true;
}
return s[i - 1] == p[j - 1];
};
for (int i = 0; i <= x; ++i) {
for (int j = 1; j <= y; ++j) {
bool t = true;
if (p[j - 1] == '*') {
if (m(i, j - 1)) {
t = dp[i - 1][j] || dp[i][j-2];
}else{
t = dp[i][j - 2];
}
}
else {
if (m(i, j)) {
t = dp[i - 1][j - 1];
}else{
t = false;
}
}
dp[i][j] = t;
}
}
return dp[x][y];
}
};