🧠
Top-Down DP with Memoization
💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency while preserving the recursive intuition.
Intuition
Use the same recursion as brute force but store results of subproblems (i,j) in a cache to avoid repeated work.
Algorithm
- Initialize a memo dictionary to store results for (i,j).
- If (i,j) in memo, return stored result.
- Apply same recursive logic as brute force.
- Store result in memo before returning.
💡 Memoization turns exponential recursion into polynomial by remembering answers to subproblems.
Recurrence:Same as brute force: match(i,j) = (p[j] == s[i] or p[j] == '.') and match(i+1,j+1) or (p[j+1] == '*' and (match(i,j+2) or (first_match and match(i+1,j))))
def isMatch(s: str, p: str) -> bool:
memo = {}
def dfs(i, j):
if (i, j) in memo:
return memo[(i, j)]
if j == len(p):
ans = i == len(s)
else:
first_match = i < len(s) and (p[j] == s[i] or p[j] == '.')
if j + 1 < len(p) and p[j+1] == '*':
ans = dfs(i, j+2) or (first_match and dfs(i+1, j))
else:
ans = first_match and dfs(i+1, j+1)
memo[(i, j)] = ans
return ans
return dfs(0, 0)
if __name__ == '__main__':
print(isMatch('aab', 'c*a*b')) # True
Line Notes
memo = {}Cache to store results of subproblems
if (i, j) in memo:Return cached result if available
if j == len(p):Base case: pattern exhausted
memo[(i, j)] = ansStore computed result before returning
return dfs(0, 0)Start matching from beginning of s and p
import java.util.*;
public class Solution {
private Map<String, Boolean> memo = new HashMap<>();
public boolean isMatch(String s, String p) {
return dfs(s, p, 0, 0);
}
private boolean dfs(String s, String p, int i, int j) {
String key = i + "," + j;
if (memo.containsKey(key)) return memo.get(key);
boolean ans;
if (j == p.length()) {
ans = i == s.length();
} else {
boolean firstMatch = i < s.length() && (p.charAt(j) == s.charAt(i) || p.charAt(j) == '.');
if (j + 1 < p.length() && p.charAt(j + 1) == '*') {
ans = dfs(s, p, i, j + 2) || (firstMatch && dfs(s, p, i + 1, j));
} else {
ans = firstMatch && dfs(s, p, i + 1, j + 1);
}
}
memo.put(key, ans);
return ans;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.isMatch("aab", "c*a*b")); // true
}
}
Line Notes
private Map<String, Boolean> memo = new HashMap<>();Cache for memoization
if (memo.containsKey(key)) return memo.get(key);Return cached result if present
if (j == p.length())Base case: pattern exhausted
memo.put(key, ans);Store result before returning
return dfs(s, p, 0, 0);Start recursion from beginning
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
class Solution {
unordered_map<string, bool> memo;
public:
bool dfs(const string& s, const string& p, int i, int j) {
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
bool ans;
if (j == p.size()) ans = (i == s.size());
else {
bool firstMatch = i < s.size() && (p[j] == s[i] || p[j] == '.');
if (j + 1 < p.size() && p[j + 1] == '*') {
ans = dfs(s, p, i, j + 2) || (firstMatch && dfs(s, p, i + 1, j));
} else {
ans = firstMatch && dfs(s, p, i + 1, j + 1);
}
}
memo[key] = ans;
return ans;
}
bool isMatch(string s, string p) {
return dfs(s, p, 0, 0);
}
};
int main() {
Solution sol;
cout << boolalpha << sol.isMatch("aab", "c*a*b") << endl; // true
return 0;
}
Line Notes
unordered_map<string, bool> memo;Cache for memoization
if (memo.count(key)) return memo[key];Return cached result if available
if (j == p.size()) ans = (i == s.size());Base case: pattern exhausted
memo[key] = ans;Store result before returning
return dfs(s, p, 0, 0);Start recursion from beginning
function isMatch(s, p) {
const memo = new Map();
function dfs(i, j) {
const key = i + ',' + j;
if (memo.has(key)) return memo.get(key);
let ans;
if (j === p.length) {
ans = i === s.length;
} else {
const firstMatch = i < s.length && (p[j] === s[i] || p[j] === '.');
if (j + 1 < p.length && p[j + 1] === '*') {
ans = dfs(i, j + 2) || (firstMatch && dfs(i + 1, j));
} else {
ans = firstMatch && dfs(i + 1, j + 1);
}
}
memo.set(key, ans);
return ans;
}
return dfs(0, 0);
}
console.log(isMatch('aab', 'c*a*b')); // true
Line Notes
const memo = new Map();Cache for memoization
if (memo.has(key)) return memo.get(key);Return cached result if present
if (j === p.length)Base case: pattern exhausted
memo.set(key, ans);Store result before returning
return dfs(0, 0);Start recursion from beginning
TimeO(m*n)
SpaceO(m*n) due to memo and recursion stack
Each subproblem (i,j) is computed once and stored, reducing exponential to polynomial time.
💡 For strings of length 1000, this means up to 1 million subproblems, which is feasible.
Interview Verdict: Accepted / Efficient for interview coding
Memoization is a practical optimization that balances clarity and performance.