本题和leetcode10.正则表达式类似,本站也有关于第10题的题解。相比于第十题,本地的情况少一些,更加简单。
题目
https://leetcode.cn/problems/wildcard-matching/
给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?'
和 '*'
匹配规则的通配符匹配:
'?'
可以匹配任何单个字符。'*'
可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 示例 1: 输入:s = "aa", p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。
示例 2: 输入:s = "aa", p = "*" 输出:true 解释:'*' 可以匹配任意字符串。
示例 3: 输入:s = "cb", p = "?a" 输出:false 解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
|
提示:
1 2 3
| 0 <= s.length, p.length <= 2000 s 仅由小写英文字母组成 p 仅由小写英文字母、'?' 或 '*' 组成
|
思路
本题的星号独立存在,不再像第十题一样是和前一个匹配符绑定的了。所以最终的情况就少了一些。下面的题解是基于第十题之上进行的修改。
本题可以用动态规划的思路,先定义dp数组,用的是非常常见的定义方式,在很多动态规划的题目中都是用这个方式定义的:
dp[i][j]
为s中i之前,p中j之前(不包括i、j)的字符串能否匹配成功;- 换句话说:
dp[i][j]
为s中前i个和p中前j个字符能否匹配成功;
根据这个定义,需要将dp数组构建如下
1 2 3
| vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1, false));
|
随后是定义递归方程,分为三种情况:
这种情况的递归方程比较简单,我们判断s[i-1] == p[j-1]
就能知道当前是否能继续往后匹配了。规划方程如下:
1
| dp[i][j] = dp[i - 1][j - 1] && s[i - 1] == p[j - 1];
|
其中dp[i - 1][j - 1]
代表的是s中前i-1个字符和p中前j-1个字符是否能成功匹配,是“上一次”计算的结果。
这种情况其实和是字母没区别,因为'?'
可以当作任意一个字母来判断。规划方程如下
1
| dp[i][j] = dp[i - 1][j - 1] && p[j - 1] == '?';
|
此时就需要分类讨论了,分为两种情况
理论上来说还会有匹配2个及以上字符的情况,但是我们这里不需要讨论这种情况,因为我们dp数组在单次循环中,s和p里面的字符都是只会增长1个的,只是引入了1个新的字符,那里有匹配俩个及以上字符的情况呢?
- 当我们匹配0个字符的时候,就相当于是把星号
p[j-1]
从p字符串中删除,此时能否匹配就取决于dp[i][j-1]
是否为真了。 - 当我们匹配1个字符的时候,就相当于使用星号匹配了
s[i-1]
,此时能否继续匹配就取决于dp[i-1][j]
是否为真了。
思考匹配和不匹配时需要沿用哪一个dp结果,可以走这个思路:我们不使用当前的星号进行匹配的时候,相当于从p里面删除当前p[j-1]
星号,所以结果是dp[i][j-1]
(j前一位,i不变);如果我们使用当前星号进行匹配,就相当于从s中删除s[i-1]
,所以结果是dp[i-1][j]
(i前一位,j不变);
代码
最终的完整代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Solution { public: bool isMatch(string s, string p) { vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1, false)); dp[0][0] = true; for (size_t j = 1; j < dp[0].size(); j++) { dp[0][j] = p[j - 1] == '*' && dp[0][j - 1]; }
for (size_t i = 1; i < dp.size(); i++) { for (size_t j = 1; j < dp[i].size(); j++) { if (p[j - 1] == '*') { bool result1 = dp[i][j - 1]; bool result2 = dp[i - 1][j];
dp[i][j] = result1 || result2; } else { bool result1 = dp[i - 1][j - 1] && s[i - 1] == p[j - 1]; bool result2 = dp[i - 1][j - 1] && p[j - 1] == '?'; dp[i][j] = result1 || result2; } } }
return dp[s.size()][p.size()]; } };
|
这里顺带给出降重成一维数组之后的代码,合并了部分状态的判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution { public: bool isMatch(string s, string p) { vector<bool> dp(p.size() + 1, false); dp[0] = true; for (size_t j = 1; j < dp.size(); j++) { dp[j] = p[j - 1] == '*' && dp[j - 1]; }
for (size_t i = 1; i <= s.size(); i++) { bool prev = dp[0]; dp[0] = false;
for (int j = 1; j <= p.size(); j++) { bool temp = dp[j]; if (p[j - 1] == '*') { dp[j] = dp[j - 1] || dp[j]; } else { dp[j] = prev && (p[j - 1] == '?' || s[i - 1] == p[j - 1]); } prev = temp; } }
return dp[p.size()]; } };
|