🧠
Top-Down DP with Memoization
💡 Memoization caches results of subproblems to avoid recomputation, drastically improving efficiency while retaining the recursive structure.
Intuition
Use recursion as before but store results of dp[i][j] so repeated calls return cached results instead of recomputing.
Algorithm
- Initialize a 2D cache to store results for each pair (i, j).
- Recursively compute results, checking cache before recursion.
- If elements match, add 1 plus result of next indices.
- Otherwise, take max of skipping one element from either array.
💡 Memoization transforms exponential recursion into polynomial time by pruning duplicate computations.
Recurrence:dp[i][j] = 1 + dp[i+1][j+1] if nums1[i] == nums2[j] else max(dp[i+1][j], dp[i][j+1])
def max_uncrossed_lines(nums1, nums2):
from functools import lru_cache
@lru_cache(None)
def dfs(i, j):
if i == len(nums1) or j == len(nums2):
return 0
if nums1[i] == nums2[j]:
return 1 + dfs(i+1, j+1)
else:
return max(dfs(i+1, j), dfs(i, j+1))
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
print(max_uncrossed_lines([1,4,2], [1,2,4])) # Output: 2
Line Notes
from functools import lru_cacheImports memoization decorator to cache function results
@lru_cache(None)Caches dfs results for each (i, j) to avoid recomputation
if i == len(nums1) or j == len(nums2)Base case to stop recursion
return max(dfs(i+1, j), dfs(i, j+1))Explore skipping elements with cached results
import java.util.*;
public class Solution {
private int[][] memo;
public int maxUncrossedLines(int[] nums1, int[] nums2) {
memo = new int[nums1.length][nums2.length];
for (int[] row : memo) Arrays.fill(row, -1);
return dfs(nums1, nums2, 0, 0);
}
private int dfs(int[] nums1, int[] nums2, int i, int j) {
if (i == nums1.length || j == nums2.length) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (nums1[i] == nums2[j]) {
memo[i][j] = 1 + dfs(nums1, nums2, i + 1, j + 1);
} else {
memo[i][j] = Math.max(dfs(nums1, nums2, i + 1, j), dfs(nums1, nums2, i, j + 1));
}
return memo[i][j];
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxUncrossedLines(new int[]{1,4,2}, new int[]{1,2,4})); // 2
}
}
Line Notes
private int[][] memo;2D array to cache results for subproblems
for (int[] row : memo) Arrays.fill(row, -1);Initialize memo with -1 to indicate uncomputed states
if (memo[i][j] != -1) return memo[i][j];Return cached result if available
memo[i][j] = Math.max(...)Store computed result to avoid recomputation
#include <iostream>
#include <vector>
using namespace std;
class Solution {
vector<vector<int>> memo;
public:
int dfs(const vector<int>& nums1, const vector<int>& nums2, int i, int j) {
if (i == nums1.size() || j == nums2.size()) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (nums1[i] == nums2[j])
memo[i][j] = 1 + dfs(nums1, nums2, i + 1, j + 1);
else
memo[i][j] = max(dfs(nums1, nums2, i + 1, j), dfs(nums1, nums2, i, j + 1));
return memo[i][j];
}
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
memo.assign(nums1.size(), vector<int>(nums2.size(), -1));
return dfs(nums1, nums2, 0, 0);
}
};
int main() {
Solution sol;
vector<int> nums1 = {1,4,2};
vector<int> nums2 = {1,2,4};
cout << sol.maxUncrossedLines(nums1, nums2) << endl; // 2
return 0;
}
Line Notes
vector<vector<int>> memo;2D vector to store computed results
memo.assign(..., -1);Initialize memo with -1 to mark unvisited states
if (memo[i][j] != -1) return memo[i][j];Return cached result if available
memo[i][j] = max(...)Store computed result to avoid recomputation
function maxUncrossedLines(nums1, nums2) {
const memo = Array(nums1.length).fill(null).map(() => Array(nums2.length).fill(-1));
function dfs(i, j) {
if (i === nums1.length || j === nums2.length) return 0;
if (memo[i][j] !== -1) return memo[i][j];
if (nums1[i] === nums2[j]) {
memo[i][j] = 1 + dfs(i + 1, j + 1);
} else {
memo[i][j] = Math.max(dfs(i + 1, j), dfs(i, j + 1));
}
return memo[i][j];
}
return dfs(0, 0);
}
console.log(maxUncrossedLines([1,4,2], [1,2,4])); // 2
Line Notes
const memo = Array(...).fill(-1);Initialize 2D memo array with -1 for unvisited states
if (memo[i][j] !== -1) return memo[i][j];Return cached result if available
memo[i][j] = Math.max(...)Store computed result to avoid recomputation
return dfs(0, 0);Start recursion from beginning of both arrays
TimeO(m*n)
SpaceO(m*n) for memoization cache and recursion stack
Each subproblem (i,j) is computed once and stored, reducing exponential calls to polynomial.
💡 For arrays of length 500, this means at most 250,000 subproblems, which is efficient.
Interview Verdict: Accepted
This approach is efficient enough for typical constraints and is a common interview solution.