本题在LeetCode官方中的难度评级为Hard,归属类型为动态规划。
前言
动态规划一直是算法中的难点,难在每道题虽然有大致相同的特点,但是又有独到之处,让人难以寻找其中的规律,也就是说想汇总成一套非常完整的模板基本是不可能的。本文将利用动态规划来解决LeetCode第44题,该题非常之典型,希望可以帮到大家,让我们能从题解中领悟到动态规划的中心思想,找到一套属于自己的解决动态规划的套路。为了方便大家更容易领悟动态规划,可以参考本人的另一篇博文:搞定动态规划----一篇足矣
本人才疏学浅,如有错误请指正。
一、题目描述
示例:
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
二、解题思想和解题步骤
- 分析题目
从题目中我们可以得到的信息有:
- ’ ?'可以匹配任意单个字符,但不包括空字符;
- ’ * '可以匹配任意字符串,包括空字符串;
- 若无1、2两项的特殊情况,就需要 S 和 P 中的字符一一对应且相等,才能得出两字符串相匹配。
- 寻找最优子结构、得出递推方程
状态:
按照动态规划的基本套路,我们定义 dp[ i ][ j ] 数组 , 该数组表示字符串 S 的以 i 结尾的部分与字符串 P 的以 j 结尾的部分是否匹配。
状态转移:
接下来需要考虑 dp 数组如何求得的问题,我们知道字符串 P 可能有三种字符包含在其中,所以我们应该按照如下情况来考虑:
1. P[ j ] 为普通字符,即 P[ j ]为 a ~z 中的一个:
此时判定dp[ i ][ j ]为True 的条件为 dp[ i-1 ][ j-1 ]为True,且P[ j ]与S[ i ]相等,即:dp[ i ][ j ]=dp[ i-1 ][ j-1 ] && P[ j ]==S[ i ];
2. P[ j ]为字符’ ? ':
由于字符’ ? ‘可以匹配任意单个字符,所以不论与之匹配的字符S[ i ]是什么,都是能够匹配成功的,所以此时 dp[ i ][ j ]=dp[ i-1][ j-1 ] && P[ j ]==’ ? ';
3. P[ j ]为字符’ * ':
这是三种情况中最复杂的情况,因为字符’ * '可以匹配任意字符串(包括空字符串),我们无法确定在这种情况下,P[ j ]是用来匹配的字符串长度,即不知道是匹配一个字符还是两个字符,还是更多的字符,所以我们需要分情况进行讨论:
-
所匹配的字符串长度为 0 :这种情况就是说 P[ j ]匹配的是空字符,所以可得出 dp[ i ][ j ]=dp[ i ][ j-1 ];
-
所匹配的字符串长度为 1 :也就是说P[ j ]只匹配了S[ i ],那么可以得出dp[ i ][ j ]=dp[ i-1 ][ j-1 ];
-
所匹配的字符串长度为 2 :这种情况下,P[ j ]匹配了 S[ i ]和S[ i-1 ]两个字符,所以dp[ i ][ j ]=dp[ i-2 ][ j-1 ];
*
*
*
*
*
*
n. 所匹配的字符串长为n :按照上面的示例,我们很容易就可以得出,dp[ i ][ j ]=dp[ i-n ][ j-1 ];
注意:大家可能不能理解为什么不管匹配的字符串长度为多少,得出的 dp 数组的值都和 dp[ 视情况而定 ][ j-1 ]相等,这是因为字符’ * ‘可以匹配任意长度的字符串,所以可以把 P[ j ]看作是空位,和P[ j ]是’ ? '时的考虑思想一样。
根据上面的推导我们可以知道:当 P[ j ] 是’ * ’ 时,
dp[ i ][ j ]=dp[ i ][ j-1 ] | | dp[ i-1 ][ j-1 ] | | dp[ i-2 ][ j-1 ] | | dp[ i-3 ][ j-1 ] ······ dp[ i-n ][ j-1 ] ;
对于这类问题,我们通常可以通过「代数」进简化,将 i - 1 代入上述的式子得出:
dp[ i−1 ][ j ]=dp[ i−1 ][ j−1 ]∣∣dp[ i−2 ][ j−1 ]∣∣…∣∣dp[ i−k ][ j-1 ] (i>=k)
可以发现,dp[ i - 1 ][ j ] 与 f[ i ][ j ] 中的 dp[ i ][ j - 1 ] 开始的后半部分是一样的,因此有:
dp[ i ][ j ] = dp[ i ][ j - 1 ] | | dp[ i - 1 ][ j ] (i >= 1)
三、代码实现
Python代码:
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for i in range(1, n + 1):
if p[i - 1] == '*':
dp[0][i] = True
else:
break
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 1]
0.| dp[i - 1][j]
elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[m][n]
C++代码:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
dp[0][0] = true;
for (int i = 1; i <= n; ++i) {
if (p[i - 1] == '*') {
dp[0][i] = true;
}
else {
break;
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 1] | dp[i - 1][j];
}
else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};
C语言代码:
bool isMatch(char* s, char* p) {
int m = strlen(s);
int n = strlen(p);
int dp[m + 1][n + 1];
memset(dp, 0, sizeof(dp));
dp[0][0] = true;
for (int i = 1; i <= n; ++i) {
if (p[i - 1] == '*') {
dp[0][i] = true;
} else {
break;
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 1] | dp[i - 1][j];
} else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
Java代码:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 1; i <= n; ++i) {
if (p.charAt(i - 1) == '*') {
dp[0][i] = true;
} else {
break;
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
} else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
}
本文代码来源于:文章链接
总结
搞定动态规划,搞定大厂offer!