🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same subproblems, drastically improving efficiency while keeping the recursive structure.
Intuition
Store results of recursive calls in a cache so repeated calls return instantly.
Algorithm
- Initialize a memo dictionary to store results for (i, j).
- If (i, j) in memo, return stored result.
- If characters match, recurse on next indices.
- Otherwise, compute 1 + min of deleting from either string.
- Store and return the result.
💡 Memoization prunes the recursion tree by caching results, preventing exponential blowup.
Recurrence:dp(i,j) = if word1[i] == word2[j] then dp(i+1,j+1) else 1 + min(dp(i+1,j), dp(i,j+1))
def minDistance(word1, word2):
memo = {}
def dfs(i, j):
if (i, j) in memo:
return memo[(i, j)]
if i == len(word1):
memo[(i, j)] = len(word2) - j
return memo[(i, j)]
if j == len(word2):
memo[(i, j)] = len(word1) - i
return memo[(i, j)]
if word1[i] == word2[j]:
memo[(i, j)] = dfs(i+1, j+1)
else:
memo[(i, j)] = 1 + min(dfs(i+1, j), dfs(i, j+1))
return memo[(i, j)]
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
print(minDistance('sea', 'eat')) # Output: 2
Line Notes
memo = {}Cache to store results of subproblems
if (i, j) in memo:Return cached result if available
memo[(i, j)] = len(word2) - jBase case: delete all remaining chars in word2
memo[(i, j)] = len(word1) - iBase case: delete all remaining chars in word1
memo[(i, j)] = dfs(i+1, j+1)If chars match, recurse without deletion
memo[(i, j)] = 1 + min(...)If chars differ, delete one char and recurse
import java.util.*;
public class Solution {
private Map<String, Integer> memo = new HashMap<>();
public int minDistance(String word1, String word2) {
return dfs(word1, word2, 0, 0);
}
private int dfs(String w1, String w2, int i, int j) {
String key = i + "," + j;
if (memo.containsKey(key)) return memo.get(key);
if (i == w1.length()) {
memo.put(key, w2.length() - j);
return memo.get(key);
}
if (j == w2.length()) {
memo.put(key, w1.length() - i);
return memo.get(key);
}
int res;
if (w1.charAt(i) == w2.charAt(j)) {
res = dfs(w1, w2, i + 1, j + 1);
} else {
res = 1 + Math.min(dfs(w1, w2, i + 1, j), dfs(w1, w2, i, j + 1));
}
memo.put(key, res);
return res;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.minDistance("sea", "eat")); // 2
}
}
Line Notes
private Map<String, Integer> memo = new HashMap<>();Cache to store computed results
String key = i + "," + j;Unique key for memoization based on indices
if (memo.containsKey(key))Return cached result if present
memo.put(key, w2.length() - j);Base case: delete remaining chars in word2
memo.put(key, w1.length() - i);Base case: delete remaining chars in word1
memo.put(key, res);Store computed result before returning
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
class Solution {
unordered_map<string, int> memo;
int dfs(const string& w1, const string& w2, int i, int j) {
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
if (i == w1.size()) return memo[key] = w2.size() - j;
if (j == w2.size()) return memo[key] = w1.size() - i;
if (w1[i] == w2[j]) return memo[key] = dfs(w1, w2, i + 1, j + 1);
return memo[key] = 1 + min(dfs(w1, w2, i + 1, j), dfs(w1, w2, i, j + 1));
}
public:
int minDistance(string word1, string word2) {
memo.clear();
return dfs(word1, word2, 0, 0);
}
};
int main() {
Solution sol;
cout << sol.minDistance("sea", "eat") << endl; // 2
return 0;
}
Line Notes
unordered_map<string, int> memo;Cache for memoization
string key = to_string(i) + "," + to_string(j);Unique key for memo
if (memo.count(key))Return cached result if exists
memo[key] = w2.size() - j;Base case: delete remaining chars in word2
memo[key] = w1.size() - i;Base case: delete remaining chars in word1
memo[key] = 1 + min(...)Store result after deleting one char
function minDistance(word1, word2) {
const memo = new Map();
function dfs(i, j) {
const key = i + ',' + j;
if (memo.has(key)) return memo.get(key);
if (i === word1.length) {
memo.set(key, word2.length - j);
return memo.get(key);
}
if (j === word2.length) {
memo.set(key, word1.length - i);
return memo.get(key);
}
let res;
if (word1[i] === word2[j]) {
res = dfs(i + 1, j + 1);
} else {
res = 1 + Math.min(dfs(i + 1, j), dfs(i, j + 1));
}
memo.set(key, res);
return res;
}
return dfs(0, 0);
}
console.log(minDistance('sea', 'eat')); // 2
Line Notes
const memo = new Map();Cache to store computed results
const key = i + ',' + j;Unique key for memoization
if (memo.has(key))Return cached result if available
memo.set(key, word2.length - j);Base case: delete remaining chars in word2
memo.set(key, word1.length - i);Base case: delete remaining chars in word1
memo.set(key, res);Store computed result before returning
TimeO(m*n)
SpaceO(m*n) for memo and recursion stack
Each subproblem (i,j) is computed once and stored.
💡 For strings of length 100, this means 10,000 computations, which is efficient.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for interview constraints.