LeetCode/solutions/44. Wildcard Matching.md
2020-06-19 09:14:08 +08:00

169 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [44. Wildcard Matching](https://leetcode.com/problems/wildcard-matching/)
# 思路
通配符匹配问题。
* '?'匹配任意非空字符;
* '*'匹配任意字符串(包括空串)。
注意和 10. Regular Expression Matching 正则匹配的题目区分开来。
## 思路一、递归+剪枝
此题最容易想到的就是递归的思路但是naive的递归很容易超时即使是加入了记忆cache和跳过连续的星号。超时的原因是存在了大量不必要的调用即不可能返回true但我们还是调用了为了避免这个问题我们定义一个返回值有三种情况的递归函数
* 0s串可被匹配完成但p有多余字符非'*'),如 s=abcp=abcd
* 1未匹配到s串的末尾就失败了
* 2成功匹配。
这样表示的目的是为了剪枝。我们来看看递归函数:
* 递归出口:
* 若p为空如果s也为空当然返回2而如果s不为空返回则 1
* 否则若s为空且`p[0] != '*'`则返回0
* 否则,我们如果第一个字符能匹配上,即`s[i] == p[j] || p[j] == '?'`,那返回递归调用后面字符匹配结果即可。
* 否则,要匹配只能寄希望于'*'了,所以如果`p[0] == '*'`我们首先要跳过连续的星号因为连续的星号和一个星号是完全等价的然后我们用一个for循环分别让星号匹配空串、s的第一个字符、s的前两个字符、...及整个s串对每种情况分别调用递归函数这个过程就需要最重要的剪枝了
* 若某次递归调用的返回值为2即可返回2函数结束这很好理解。
* 若某次递归调用的返回值为0这说明此时s的剩余字符太少了或者说 p 的剩余字符太多了我设此时星号匹配s的前k个字符那如果星号匹配的是s的前k+1个字符那么s的剩余字符会比现在还少一个因此接下来的情况都只可能返回0。因此这种情况我们直接返回0即可函数结束。
* 否则那么匹配失败返回1。
时间亲测8ms
## 思路二、动归
状态定义为
```
dp[i][j] = true 表示 s[0,1,..,i-1]和p[0,1,..,j-1]]能匹配上
```
需要仔细考虑一下初始状态,首先我们知道两个空串是能匹配的,所以`dp[0][0]=true`,另外由于星号可匹配空串,所以如果`p[0...k-1] = '*'`,我们需要将`dp[0][1...k]`初始化为true。
接下来就是状态转移方程,根据是否是星号有两种情况:
1. `p[j-1] == '*'`那么p[j-1]既可以匹配掉当前s的字符s[i-1]也可以选择不匹配(即匹配空字符),即`p[i][j] = dp[i - 1][j] || dp[i][j - 1]`;
2. 否则就只能看p[j-1]和s[i-1]是否能够硬性匹配了,而且还有考虑前面的匹配情况,即`dp[i][j] = (s[i - 1] == p[j - 1] || p[j - 1] == '?') && dp[i - 1][j - 1]`。
可见状态数组里的`dp[i][j]`只和其上(`dp[i - 1][j]`)、左上(`dp[i - 1][j - 1]`)、左(`dp[i][j - 1]`边的元素有关所以我们可以考虑用滚动数组优化空间以及剪枝若上一行元素和当前行第一个元素dp[i][0]都为false的话直接返回false就可以了
时间复杂度O(mn)亲测96ms空间复杂度可优化至线性。
## 思路三、0ms
此题还有一个比较难想但是亲测最快的方法。
基本的思路就是**贪婪**地一一匹配,**如果在某处匹配失败了那么回到最近的星号处将本次该星号匹配的字符数加上1继续匹配**即若本次该星号匹配了k个字符那么再多匹配一个字符匹配k+1个字符重新尝试匹配。
为此我们用jStar来表示p串中星号的位置还有用iStar表示星号匹配到s串中的最后位置iStar和jStar均初始化为 -1表示还没遇到星号。另外设置两个工作指针i和j分别指向s和p的当前字符初始均为0。
具体匹配过程:
while循环直到 i 等于s串的长度
* 如果`j < n && (s[i] == p[j] || p[j] == '?')`说明硬匹配成功两个工作指针后移即可
* 否则如果`j < n && p[j] == '*'`表明遇到了最新的星号需要更新iStar和jStar`istar = i; jstar = j;`,然后我们后移工作指针 j但是不移 i 代表这个星号匹配了0个字符所以下次还要从当前字符开始
* 否则表示当前匹配失败了那唯一的希望就是回到最近匹配的星号处所以前提是`istar >= 0`),让这个星号多匹配一个字符然后再重新向后匹配,即`i = ++istar; j = jstar + 1`。
* 否则唯一的希望也破灭了只能返回false函数结束。
while循环结束。此时s串已匹配完毕如果p串也匹配完毕或者剩下的全是星号则说明匹配成功否则匹配失败。
时间复杂度最坏O(mn)但是亲测是最快的0ms空间复杂度O(1)
# C++
## 思路一
``` C++
class Solution {
private:
int sn, pn; // s.size, p.size
int helper(string& s, string& p, int i, int j) {
/*
0: s被匹配完成但p有多余字符(非*), 如s = ab, p = abc
1: 未匹配到s串的末尾就失败了
2: 成功匹配
*/
if(j == pn) return i == sn ? 2 : 1;
if (i == sn && p[j] != '*') return 0;
if (s[i] == p[j] || p[j] == '?')
return helper(s, p, i + 1, j + 1);
if (p[j] == '*') {
while(j+1 < pn && p[j+1] == '*') j++; // 跳过连续星号
for (int k = 0; k <= sn - i; ++k) {
int res = helper(s, p, i + k, j + 1);
if (res != 1 ) return res; // res = 0 or 2, 剪枝
}
}
return 1;
}
public:
bool isMatch(string s, string p) {
sn = s.size(); pn = p.size();
return helper(s, p, 0, 0) > 1;
}
};
```
## 思路二
``` C++
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int k = 1; k <= n; ++k)
if (p[k - 1] == '*') dp[0][k] = true;
else break;
bool pre_row_exist_true = true;
for (int i = 1; i <= m; ++i) {
// 若上一行和当前行的第一个元素都为false的话直接返回false
if(!pre_row_exist_true && !dp[i][0]) return false;
pre_row_exist_true = dp[i][0];
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*')
dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
else
dp[i][j] = (s[i - 1] == p[j - 1] || p[j - 1] == '?') &&
dp[i - 1][j - 1];
pre_row_exist_true = pre_row_exist_true || dp[i][j];
}
}
return dp[m][n];
}
};
```
## 思路三
``` C++
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
int m = s.size(), n = p.size(), i = 0, j = 0;
int istar = -1, jstar = -1;
while(i < m){
if(j < n && (s[i] == p[j] || p[j] == '?')){
i++;
j++;
}
else if(j < n && p[j] == '*'){
istar = i;
jstar = j++;
}
else if(istar >= 0){
i = ++istar;
j = jstar + 1;
}
else return false;
}
while(j < n && p[j] == '*') j++;
return j == n;
}
};
```