# [44. Wildcard Matching](https://leetcode.com/problems/wildcard-matching/)

# 思路

通配符匹配问题。

* '?'匹配任意非空字符；
* '*'匹配任意字符串（包括空串）。

注意和 10. Regular Expression Matching 正则匹配的题目区分开来。

## 思路一、递归+剪枝

此题最容易想到的就是递归的思路，但是naive的递归很容易超时（即使是加入了记忆cache和跳过连续的星号）。超时的原因是存在了大量不必要的调用（即不可能返回true但我们还是调用了），为了避免这个问题，我们定义一个返回值有三种情况的递归函数：

* 0：s串可被匹配完成，但p有多余字符（非'*'），如 s=abc，p=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[k] = '*'`，我们需要将`dp[0][k]初始化为``,...,k-1]`都是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;
    }
};
```