🧠
Top-Down Memoization (Recursion + Cache)
💡 Memoization avoids recomputing the same subproblems by caching results, drastically improving performance while keeping the recursive structure.
Intuition
Store results of recursive calls in a cache keyed by indices to prevent exponential recomputation.
Algorithm
- Initialize a 2D cache to store results for each pair of indices.
- If result for (i, j) is cached, return it immediately.
- Otherwise, compute recursively as in brute force and store the result in cache.
- Return the cached result.
💡 Memoization transforms exponential recursion into polynomial time by reusing results.
def minDistance(word1: str, word2: str) -> int:
from functools import lru_cache
@lru_cache(None)
def dfs(i, j):
if i == len(word1):
return len(word2) - j
if j == len(word2):
return len(word1) - i
if word1[i] == word2[j]:
return dfs(i + 1, j + 1)
insert = 1 + dfs(i, j + 1)
delete = 1 + dfs(i + 1, j)
replace = 1 + dfs(i + 1, j + 1)
return min(insert, delete, replace)
return dfs(0, 0)
# Example usage:
if __name__ == '__main__':
print(minDistance("horse", "ros")) # Output: 3
Line Notes
@lru_cache(None)Caches results of dfs calls to avoid recomputation
def dfs(i, j):Recursive helper with memoization
if i == len(word1):Base case: insert remaining chars if word1 exhausted
if j == len(word2):Base case: delete remaining chars if word2 exhausted
if word1[i] == word2[j]:No operation needed if chars match
insert = 1 + dfs(i, j + 1)Try insertion
delete = 1 + dfs(i + 1, j)Try deletion
replace = 1 + dfs(i + 1, j + 1)Try replacement
return min(insert, delete, replace)Return minimum cost
import java.util.*;
public class EditDistance {
private static Integer[][] memo;
public static int minDistance(String word1, String word2) {
memo = new Integer[word1.length() + 1][word2.length() + 1];
return dfs(word1, word2, 0, 0);
}
private static int dfs(String w1, String w2, int i, int j) {
if (memo[i][j] != null) return memo[i][j];
if (i == w1.length()) return w2.length() - j;
if (j == w2.length()) return w1.length() - i;
if (w1.charAt(i) == w2.charAt(j)) {
memo[i][j] = dfs(w1, w2, i + 1, j + 1);
return memo[i][j];
}
int insert = 1 + dfs(w1, w2, i, j + 1);
int delete = 1 + dfs(w1, w2, i + 1, j);
int replace = 1 + dfs(w1, w2, i + 1, j + 1);
memo[i][j] = Math.min(insert, Math.min(delete, replace));
return memo[i][j];
}
public static void main(String[] args) {
System.out.println(minDistance("horse", "ros")); // Output: 3
}
}
Line Notes
private static Integer[][] memo;Memoization cache to store results for subproblems
if (memo[i][j] != null) return memo[i][j];Return cached result if available
if (i == w1.length())Base case: insert remaining chars
if (j == w2.length())Base case: delete remaining chars
if (w1.charAt(i) == w2.charAt(j))No operation needed if chars match
int insert = 1 + dfs(w1, w2, i, j + 1);Try insertion
int delete = 1 + dfs(w1, w2, i + 1, j);Try deletion
int replace = 1 + dfs(w1, w2, i + 1, j + 1);Try replacement
memo[i][j] = Math.min(insert, Math.min(delete, replace));Cache minimum cost
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> memo;
int dfs(const string& w1, const string& w2, int i, int j) {
if (memo[i][j] != -1) return memo[i][j];
if (i == (int)w1.size()) return (int)w2.size() - j;
if (j == (int)w2.size()) return (int)w1.size() - i;
if (w1[i] == w2[j]) {
memo[i][j] = dfs(w1, w2, i + 1, j + 1);
return memo[i][j];
}
int insert_op = 1 + dfs(w1, w2, i, j + 1);
int delete_op = 1 + dfs(w1, w2, i + 1, j);
int replace_op = 1 + dfs(w1, w2, i + 1, j + 1);
memo[i][j] = min({insert_op, delete_op, replace_op});
return memo[i][j];
}
int minDistance(string word1, string word2) {
memo.assign(word1.size() + 1, vector<int>(word2.size() + 1, -1));
return dfs(word1, word2, 0, 0);
}
int main() {
cout << minDistance("horse", "ros") << endl; // Output: 3
return 0;
}
Line Notes
vector<vector<int>> memo;2D vector to cache results for subproblems
if (memo[i][j] != -1) return memo[i][j];Return cached result if computed
if (i == (int)w1.size())Base case: insert remaining chars
if (j == (int)w2.size())Base case: delete remaining chars
if (w1[i] == w2[j])No operation needed if chars match
int insert_op = 1 + dfs(w1, w2, i, j + 1);Try insertion
int delete_op = 1 + dfs(w1, w2, i + 1, j);Try deletion
int replace_op = 1 + dfs(w1, w2, i + 1, j + 1);Try replacement
memo[i][j] = min({insert_op, delete_op, replace_op});Cache minimum cost
function minDistance(word1, word2) {
const memo = Array(word1.length + 1).fill(null).map(() => Array(word2.length + 1).fill(-1));
function dfs(i, j) {
if (memo[i][j] !== -1) return memo[i][j];
if (i === word1.length) return word2.length - j;
if (j === word2.length) return word1.length - i;
if (word1[i] === word2[j]) {
memo[i][j] = dfs(i + 1, j + 1);
return memo[i][j];
}
const insertOp = 1 + dfs(i, j + 1);
const deleteOp = 1 + dfs(i + 1, j);
const replaceOp = 1 + dfs(i + 1, j + 1);
memo[i][j] = Math.min(insertOp, deleteOp, replaceOp);
return memo[i][j];
}
return dfs(0, 0);
}
// Example usage:
console.log(minDistance("horse", "ros")); // Output: 3
Line Notes
const memo = Array(word1.length + 1).fill(null).map(() => Array(word2.length + 1).fill(-1));Initialize 2D cache with -1 indicating uncomputed states
if (memo[i][j] !== -1) return memo[i][j];Return cached result if available
if (i === word1.length)Base case: insert remaining chars
if (j === word2.length)Base case: delete remaining chars
if (word1[i] === word2[j])No operation needed if chars match
const insertOp = 1 + dfs(i, j + 1);Try insertion
const deleteOp = 1 + dfs(i + 1, j);Try deletion
const replaceOp = 1 + dfs(i + 1, j + 1);Try replacement
memo[i][j] = Math.min(insertOp, deleteOp, replaceOp);Cache minimum cost
Each subproblem (i,j) is computed once and stored, with m and n being string lengths.
💡 For strings of length 1000, this means about 1 million computations, which is feasible.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for large inputs and is a common interview expectation.