1. Top-Down Recursion with Memoization
Intuition
Define match(i, j) returning whether s[i...] matches p[j...]. If j == p.length, return i == s.length. Check if firstMatch = (i < s.length && (s[i] == p[j] || p[j] == '.')). If j + 1 < p.length && p[j + 1] == '*': return match(i, j + 2) || (firstMatch && match(i + 1, j)). Else: return firstMatch && match(i + 1, j + 1). Memoize with memo[i][j].
When to apply: When deriving the recurrence relationship from first principles.
Algorithm
- Create
memomatrix of size (N + 1) x (M + 1) initialized to -1. - Helper
dp(i, j): - If
j == m: returni == n. - If
memo[i][j] != -1: returnmemo[i][j]. -
firstMatch = (i < n && (s[i] == p[j] || p[j] == '.')). - If
j + 1 < m && p[j + 1] == '*': -
ans = dp(i, j + 2) || (firstMatch && dp(i + 1, j)). - Else:
-
ans = firstMatch && dp(i + 1, j + 1). - Return
memo[i][j] = ans.
#include <iostream>#include <string>#include <vector> class Solution { int memo[25][35]; bool dp(int i, int j, const std::string& s, const std::string& p) { if (j == (int)p.size()) return i == (int)s.size(); if (memo[i][j] != -1) return memo[i][j]; bool firstMatch = (i < (int)s.size() && (s[i] == p[j] || p[j] == '.')); bool ans = false; if (j + 1 < (int)p.size() && p[j + 1] == '*') { ans = dp(i, j + 2, s, p) || (firstMatch && dp(i + 1, j, s, p)); } else { ans = firstMatch && dp(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 <= 24; ++i) for (int j = 0; j <= 34; ++j) memo[i][j] = -1; return dp(0, 0, s, p); }}; int main() { Solution sol; std::cout << (sol.isMatch("aa", "a*") ? "true" : "false") << std::endl; // true std::cout << (sol.isMatch("ab", ".*") ? "true" : "false") << std::endl; // true std::cout << (sol.isMatch("aa", "a") ? "true" : "false") << std::endl; // false return 0;}Time & Space Complexity
- Time complexity:
O(N * M)— There are (N + 1) * (M + 1) states, each evaluated in O(1). - Space complexity:
O(N * M)
Why Brute Force Doesn't Scale:
2D Dynamic Programming table avoids exponential branching of naive backtracking.
State transitions only depend on the current and previous rows.