🧠
Top-Down DP with Memoization
💡 Memoization avoids repeated work by caching results of subproblems, making the recursive approach efficient.
Intuition
Store results of dfs(i,j) calls so that repeated states are computed once, reducing exponential to polynomial time.
Algorithm
- Initialize a memo dictionary to store results for (i,j).
- Perform recursive dfs with memo checks before computing.
- Return cached result if available to avoid recomputation.
- Combine results from taking characters from s1 or s2.
💡 Memoization transforms exponential recursion into polynomial by pruning repeated calls.
Recurrence:dp(i,j) = (s1[i] == s3[i+j] and dp(i+1,j)) or (s2[j] == s3[i+j] and dp(i,j+1))
def isInterleave(s1, s2, s3):
memo = {}
def dfs(i, j):
if (i, j) in memo:
return memo[(i, j)]
if i == len(s1) and j == len(s2):
return True
if i < len(s1) and s1[i] == s3[i+j]:
if dfs(i+1, j):
memo[(i, j)] = True
return True
if j < len(s2) and s2[j] == s3[i+j]:
if dfs(i, j+1):
memo[(i, j)] = True
return True
memo[(i, j)] = False
return False
if len(s1) + len(s2) != len(s3):
return False
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
print(isInterleave('aabcc', 'dbbca', 'aadbbcbcac')) # True
print(isInterleave('aabcc', 'dbbca', 'aadbbbaccc')) # False
Line Notes
memo = {}Initialize cache to store results of subproblems
if (i, j) in memo:Return cached result if available to avoid recomputation
memo[(i, j)] = TrueCache positive result for current state
memo[(i, j)] = FalseCache negative result to prune future calls
if len(s1) + len(s2) != len(s3):Length check to quickly reject invalid inputs
import java.util.*;
public class Solution {
private Map<String, Boolean> memo = new HashMap<>();
public boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) return false;
return dfs(s1, 0, s2, 0, s3);
}
private boolean dfs(String s1, int i, String s2, int j, String s3) {
String key = i + "," + j;
if (memo.containsKey(key)) return memo.get(key);
if (i == s1.length() && j == s2.length()) return true;
if (i < s1.length() && s1.charAt(i) == s3.charAt(i + j)) {
if (dfs(s1, i + 1, s2, j, s3)) {
memo.put(key, true);
return true;
}
}
if (j < s2.length() && s2.charAt(j) == s3.charAt(i + j)) {
if (dfs(s1, i, s2, j + 1, s3)) {
memo.put(key, true);
return true;
}
}
memo.put(key, false);
return false;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.isInterleave("aabcc", "dbbca", "aadbbcbcac")); // true
System.out.println(sol.isInterleave("aabcc", "dbbca", "aadbbbaccc")); // false
}
}
Line Notes
private Map<String, Boolean> memo = new HashMap<>();Cache to store results of subproblems
String key = i + "," + j;Create unique key for memoization
if (memo.containsKey(key)) return memo.get(key);Return cached result if available
memo.put(key, true);Cache positive result
memo.put(key, false);Cache negative result
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
class Solution {
unordered_map<string, bool> memo;
bool dfs(const string& s1, int i, const string& s2, int j, const string& s3) {
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
if (i == s1.size() && j == s2.size()) return true;
if (i < s1.size() && s1[i] == s3[i + j]) {
if (dfs(s1, i + 1, s2, j, s3)) {
memo[key] = true;
return true;
}
}
if (j < s2.size() && s2[j] == s3[i + j]) {
if (dfs(s1, i, s2, j + 1, s3)) {
memo[key] = true;
return true;
}
}
memo[key] = false;
return false;
}
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
return dfs(s1, 0, s2, 0, s3);
}
};
int main() {
Solution sol;
cout << boolalpha << sol.isInterleave("aabcc", "dbbca", "aadbbcbcac") << endl; // true
cout << boolalpha << sol.isInterleave("aabcc", "dbbca", "aadbbbaccc") << endl; // false
return 0;
}
Line Notes
unordered_map<string, bool> memo;Cache to store results of subproblems
string key = to_string(i) + "," + to_string(j);Unique key for memoization
if (memo.count(key)) return memo[key];Return cached result if available
memo[key] = true;Cache positive result
memo[key] = false;Cache negative result
function isInterleave(s1, s2, s3) {
if (s1.length + s2.length !== s3.length) return false;
const memo = new Map();
function dfs(i, j) {
const key = i + ',' + j;
if (memo.has(key)) return memo.get(key);
if (i === s1.length && j === s2.length) return true;
if (i < s1.length && s1[i] === s3[i + j]) {
if (dfs(i + 1, j)) {
memo.set(key, true);
return true;
}
}
if (j < s2.length && s2[j] === s3[i + j]) {
if (dfs(i, j + 1)) {
memo.set(key, true);
return true;
}
}
memo.set(key, false);
return false;
}
return dfs(0, 0);
}
console.log(isInterleave('aabcc', 'dbbca', 'aadbbcbcac')); // true
console.log(isInterleave('aabcc', 'dbbca', 'aadbbbaccc')); // false
Line Notes
const memo = new Map();Cache to store results of subproblems
const key = i + ',' + j;Unique key for memoization
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, true);Cache positive result
memo.set(key, false);Cache negative result
TimeO(m*n)
SpaceO(m*n) due to memo and recursion stack
Each state (i,j) is computed once and cached, reducing exponential calls to polynomial.
💡 For strings of length 1000, this approach does about 1 million computations, which is feasible.
Interview Verdict: Accepted
Memoization makes the recursive solution efficient enough for large inputs.