1. Top-Down Memoized Recursion
Intuition
Define solve(i, j) checking if s[i...] matches p[j...]. If j == p.length, return i == s.length. If p[j] == '*': return solve(i, j + 1) || (i < s.length && solve(i + 1, j)). If i < s.length && (p[j] == '?' || s[i] == p[j]): return solve(i + 1, j + 1). Memoize results in memo[i][j].
When to apply: Basic recursive baseline formulation.
Algorithm
- Initialize
memotable of size(s.length + 1) x (p.length + 1)with -1. - Helper
dp(i, j): - If
j == p.length: returni == s.length. - If
memo[i][j] != -1: returnmemo[i][j]. - If
p[j] == '*': result isdp(i, j + 1) || (i < s.length && dp(i + 1, j)). - Else if
i < s.length && (p[j] == '?' || s[i] == p[j]): result isdp(i + 1, j + 1). - Else: result is false.
- Store and return
memo[i][j].
#include <iostream>#include <string>#include <vector> class Solution { int memo[2005][2005]; bool solve(int i, int j, const std::string& s, const std::string& p) { if (j == p.size()) return i == s.size(); if (memo[i][j] != -1) return memo[i][j]; bool ans = false; if (p[j] == '*') { ans = solve(i, j + 1, s, p) || (i < s.size() && solve(i + 1, j, s, p)); } else if (i < s.size() && (p[j] == '?' || s[i] == p[j])) { ans = solve(i + 1, j + 1, s, p); } return memo[i][j] = ans; }public: bool isMatch(std::string s, std::string p) { for (int i = 0; i <= s.size(); ++i) { for (int j = 0; j <= p.size(); ++j) { memo[i][j] = -1; } } return solve(0, 0, s, p); }}; int main() { Solution sol; std::cout << (sol.isMatch("adceb", "*a*b") ? "true" : "false") << std::endl; // true std::cout << (sol.isMatch("cb", "?a") ? "true" : "false") << std::endl; // false return 0;}Time & Space Complexity
- Time complexity:
O(S * P)— There are S * P unique subproblems, each taking O(1) transitions. - Space complexity:
O(S * P)
Why Brute Force Doesn't Scale:
Greedy pointer backtracking avoids full DP matrix allocation.
Strict O(1) auxiliary variables.