9.6 KiB
Executable File
解题思路:
设
s的长度为n,p的长度为m;将s的第i个字符记为s_i,p的第j个字符记为p_j,将s的前i个字符组成的子字符串记为s[:i], 同理将p的前j个字符组成的子字符串记为p[:j]。因此,本题可转化为求
s[:n]是否能和p[:m]匹配。
总体思路是从 s[:1] 和 p[:1] 是否能匹配开始判断,每轮添加一个字符并判断是否能匹配,直至添加完整个字符串 s 和 p 。展开来看,假设 s[:i] 与 p[:j] 可以匹配,那么下一状态有两种:
- 添加一个字符
s_{i+1}后是否能匹配? - 添加字符
p_{j+1}后是否能匹配?
因此,本题的状态共有 m \times n 种,应定义状态矩阵 dp ,dp[i][j] 代表 s[:i] 与 p[:j] 是否可以匹配。
做好状态定义,接下来就是根据 「普通字符」 , 「.」 , 「*」三种字符的功能定义,分析出动态规划的转移方程。
动态规划解析:
状态定义: 设动态规划矩阵 dp ,dp[i][j] 代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。
转移方程: 需要注意,由于 dp[0][0] 代表的是空字符的状态, 因此 dp[i][j] 对应的添加字符是 s[i - 1] 和 p[j - 1] 。
-
当
p[j - 1] = '*'时,dp[i][j]在当以下任一情况为\text{true}时等于\text{true}:dp[i][j - 2]: 即将字符组合p[j - 2] *看作出现 0 次时,能否匹配;dp[i - 1][j]且s[i - 1] = p[j - 2]: 即让字符p[j - 2]多出现 1 次时,能否匹配;dp[i - 1][j]且p[j - 2] = '.': 即让字符'.'多出现 1 次时,能否匹配;
-
当
p[j - 1] != '*'时,dp[i][j]在当以下任一情况为\text{true}时等于\text{true}:dp[i - 1][j - 1]且s[i - 1] = p[j - 1]: 即让字符p[j - 1]多出现一次时,能否匹配;dp[i - 1][j - 1]且p[j - 1] = '.': 即将字符.看作字符s[i - 1]时,能否匹配;
初始化: 需要先初始化 dp 矩阵首行,以避免状态转移时索引越界。
dp[0][0] = true: 代表两个空字符串能够匹配。dp[0][j] = dp[0][j - 2]且p[j - 1] = '*': 首行s为空字符串,因此当p的偶数位为*时才能够匹配(即让p的奇数位出现 0 次,保持p是空字符串)。因此,循环遍历字符串p,步长为 2(即只看偶数位)。
返回值: dp 矩阵右下角字符,代表字符串 s 和 p 能否匹配。
代码:
class Solution:
def articleMatch(self, s: str, p: str) -> bool:
m, n = len(s) + 1, len(p) + 1
dp = [[False] * n for _ in range(m)]
dp[0][0] = True
for j in range(2, n, 2):
dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i][j - 2] or dp[i - 1][j] and (s[i - 1] == p[j - 2] or p[j - 2] == '.') \
if p[j - 1] == '*' else \
dp[i - 1][j - 1] and (p[j - 1] == '.' or s[i - 1] == p[j - 1])
return dp[-1][-1]
class Solution {
public boolean articleMatch(String s, String p) {
int m = s.length() + 1, n = p.length() + 1;
boolean[][] dp = new boolean[m][n];
dp[0][0] = true;
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p.charAt(j - 1) == '*';
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
dp[i][j] = p.charAt(j - 1) == '*' ?
dp[i][j - 2] || dp[i - 1][j] && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.') :
dp[i - 1][j - 1] && (p.charAt(j - 1) == '.' || s.charAt(i - 1) == p.charAt(j - 1));
}
}
return dp[m - 1][n - 1];
}
}
class Solution {
public:
bool articleMatch(string s, string p) {
int m = s.size() + 1, n = p.size() + 1;
vector<vector<bool>> dp(m, vector<bool>(n, false));
dp[0][0] = true;
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
dp[i][j] = p[j - 1] == '*' ?
dp[i][j - 2] || dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.'):
dp[i - 1][j - 1] && (p[j - 1] == '.' || s[i - 1] == p[j - 1]);
}
}
return dp[m - 1][n - 1];
}
};
以上代码利用布尔运算实现简短长度,若阅读不畅,可先理解以下代码,与文中内容一一对应:
class Solution:
def articleMatch(self, s: str, p: str) -> bool:
m, n = len(s) + 1, len(p) + 1
dp = [[False] * n for _ in range(m)]
dp[0][0] = True
# 初始化首行
for j in range(2, n, 2):
dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
# 状态转移
for i in range(1, m):
for j in range(1, n):
if p[j - 1] == '*':
if dp[i][j - 2]: dp[i][j] = True # 1.
elif dp[i - 1][j] and s[i - 1] == p[j - 2]: dp[i][j] = True # 2.
elif dp[i - 1][j] and p[j - 2] == '.': dp[i][j] = True # 3.
else:
if dp[i - 1][j - 1] and s[i - 1] == p[j - 1]: dp[i][j] = True # 1.
elif dp[i - 1][j - 1] and p[j - 1] == '.': dp[i][j] = True # 2.
return dp[-1][-1]
class Solution {
public boolean articleMatch(String s, String p) {
int m = s.length() + 1, n = p.length() + 1;
boolean[][] dp = new boolean[m][n];
dp[0][0] = true;
// 初始化首行
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p.charAt(j - 1) == '*';
// 状态转移
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
if(p.charAt(j - 1) == '*') {
if(dp[i][j - 2]) dp[i][j] = true; // 1.
else if(dp[i - 1][j] && s.charAt(i - 1) == p.charAt(j - 2)) dp[i][j] = true; // 2.
else if(dp[i - 1][j] && p.charAt(j - 2) == '.') dp[i][j] = true; // 3.
} else {
if(dp[i - 1][j - 1] && s.charAt(i - 1) == p.charAt(j - 1)) dp[i][j] = true; // 1.
else if(dp[i - 1][j - 1] && p.charAt(j - 1) == '.') dp[i][j] = true; // 2.
}
}
}
return dp[m - 1][n - 1];
}
}
class Solution {
public:
bool articleMatch(string s, string p) {
int m = s.size() + 1, n = p.size() + 1;
vector<vector<bool>> dp(m, vector<bool>(n, false));
dp[0][0] = true;
// 初始化首行
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
// 状态转移
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
if(p[j - 1] == '*') {
if(dp[i][j - 2]) dp[i][j] = true; // 1.
else if(dp[i - 1][j] && s[i - 1] == p[j - 2]) dp[i][j] = true; // 2.
else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true; // 3.
} else {
if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true; // 1.
else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true; // 2.
}
}
}
return dp[m - 1][n - 1];
}
};
复杂度分析:
- 时间复杂度
O(MN): 其中M, N分别为s和p的长度,状态转移需遍历整个dp矩阵。 - 空间复杂度
O(MN): 状态矩阵dp使用O(MN)的额外空间。


















