Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for '?' and '*' where:
'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).
Example 1:
Input: s = "aa", p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input: s = "aa", p = "*"
Output: true
Explanation: '*' matches any sequence.
Example 3:
Input: s = "cb", p = "?a"
Output: false
Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.
Constraints:
0 <= s.length, p.length <= 2000
s contains only lowercase English letters.
p contains only lowercase English letters, '?' or '*'.
Solutions
Solution 1: Memoization Search
Thinking
The first idea is recurse character by character: literals and ? match one, * eats empty or more. Correct, but without memo the * branches explode. \(|s|, |p| \le 2000\) will time out.
The bottleneck is asking the same suffix pair \((i, j)\) over and over. The state is only “does \(s[i:]\) match \(p[j:]\)”; subproblems overlap heavily.
Cache \(dfs(i, j)\). The three * moves (consume one, advance both, skip *) are transitions on that state. Memoization turns exponential search into \(O(mn)\).
We design a function \(dfs(i, j)\), which represents whether the string \(s\) starting from the \(i\)-th character matches the string \(p\) starting from the \(j\)-th character. The answer is \(dfs(0, 0)\).
The execution process of the function \(dfs(i, j)\) is as follows:
If \(i \geq \textit{len}(s)\), then \(dfs(i, j)\) is true only when \(j \geq \textit{len}(p)\) or \(p[j] = '*'\) and \(dfs(i, j + 1)\) is true.
If \(j \geq \textit{len}(p)\), then \(dfs(i, j)\) is false.
If \(p[j] = '*'\), then \(dfs(i, j)\) is true if and only if \(dfs(i + 1, j)\) or \(dfs(i + 1, j + 1)\) or \(dfs(i, j + 1)\) is true.
Otherwise, \(dfs(i, j)\) is true if and only if \(p[j] = '?'\) or \(s[i] = p[j]\) and \(dfs(i + 1, j + 1)\) is true.
To avoid repeated calculations, we use the method of memoization search and store the result of \(dfs(i, j)\) in a hash table.
The time complexity is \(O(m \times n)\), and the space complexity is \(O(m \times n)\). Where \(m\) and \(n\) are the lengths of the strings \(s\) and \(p\), respectively.
Solution 1 is already \(O(mn)\), but recursion pays a call stack and cache constants. At length \(2000\), both hurt.
What it lacks is the same transition written bottom-up. \(f[i][j]\) is the table form of \(dfs(i, j)\): fill the empty-string/* border first, then the rest. Same asymptotics, more stable constants.
We can convert the memoization search in Solution 1 into dynamic programming.
Define \(f[i][j]\) to represent whether the first \(i\) characters of string \(s\) match the first \(j\) characters of string \(p\). Initially, \(f[0][0] = \textit{true}\), indicating that two empty strings are matching. For \(j \in [1, n]\), if \(p[j-1] = '*'\), then \(f[0][j] = f[0][j-1]\).
Next, we consider the case of \(i \in [1, m]\) and \(j \in [1, n]\):
If \(p[j-1] = '*'\), then \(f[i][j] = f[i-1][j] \lor f[i][j-1] \lor f[i-1][j-1]\).
The time complexity is \(O(m \times n)\), and the space complexity is \(O(m \times n)\). Where \(m\) and \(n\) are the lengths of the strings \(s\) and \(p\), respectively.