🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same subproblems by caching results, drastically improving efficiency.
Intuition
Store the number of paths from each cell once computed, so repeated calls return cached results instead of recalculating.
Algorithm
- Initialize a 2D cache array to store results for each cell.
- Start recursion from (0,0).
- If result for current cell is cached, return it.
- Otherwise, compute paths by summing down and right moves recursively.
- Cache and return the computed result.
💡 Memoization transforms exponential recursion into polynomial time by avoiding duplicate work.
Recurrence:dp[i][j] = dp[i+1][j] + dp[i][j+1]
def uniquePaths(m, n):
memo = [[-1] * n for _ in range(m)]
def dfs(i, j):
if i == m - 1 and j == n - 1:
return 1
if i >= m or j >= n:
return 0
if memo[i][j] != -1:
return memo[i][j]
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1)
return memo[i][j]
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
print(uniquePaths(3, 7)) # Output: 28
Line Notes
memo = [[-1] * n for _ in range(m)]Initialize cache with -1 indicating uncomputed cells.
if memo[i][j] != -1:Return cached result if available to avoid recomputation.
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1)Store computed paths count for current cell.
return memo[i][j]Return cached or newly computed result.
public class UniquePaths {
public static int uniquePaths(int m, int n) {
int[][] memo = new int[m][n];
for (int i = 0; i < m; i++)
Arrays.fill(memo[i], -1);
return dfs(0, 0, m, n, memo);
}
private static int dfs(int i, int j, int m, int n, int[][] memo) {
if (i == m - 1 && j == n - 1) return 1;
if (i >= m || j >= n) return 0;
if (memo[i][j] != -1) return memo[i][j];
memo[i][j] = dfs(i + 1, j, m, n, memo) + dfs(i, j + 1, m, n, memo);
return memo[i][j];
}
public static void main(String[] args) {
System.out.println(uniquePaths(3, 7)); // Output: 28
}
}
Line Notes
int[][] memo = new int[m][n];Create 2D cache array for memoization.
Arrays.fill(memo[i], -1);Mark all cells as uncomputed initially.
if (memo[i][j] != -1) return memo[i][j];Return cached result if available.
memo[i][j] = dfs(i + 1, j, m, n, memo) + dfs(i, j + 1, m, n, memo);Cache computed paths count.
#include <iostream>
#include <vector>
using namespace std;
int dfs(int i, int j, int m, int n, vector<vector<int>>& memo) {
if (i == m - 1 && j == n - 1) return 1;
if (i >= m || j >= n) return 0;
if (memo[i][j] != -1) return memo[i][j];
memo[i][j] = dfs(i + 1, j, m, n, memo) + dfs(i, j + 1, m, n, memo);
return memo[i][j];
}
int uniquePaths(int m, int n) {
vector<vector<int>> memo(m, vector<int>(n, -1));
return dfs(0, 0, m, n, memo);
}
int main() {
cout << uniquePaths(3, 7) << endl; // Output: 28
return 0;
}
Line Notes
vector<vector<int>> memo(m, vector<int>(n, -1));Initialize memo table with -1 for uncomputed cells.
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation.
memo[i][j] = dfs(i + 1, j, m, n, memo) + dfs(i, j + 1, m, n, memo);Store computed number of paths.
return memo[i][j];Return memoized result.
function uniquePaths(m, n) {
const memo = Array.from({ length: m }, () => Array(n).fill(-1));
function dfs(i, j) {
if (i === m - 1 && j === n - 1) return 1;
if (i >= m || j >= n) return 0;
if (memo[i][j] !== -1) return memo[i][j];
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1);
return memo[i][j];
}
return dfs(0, 0);
}
console.log(uniquePaths(3, 7)); // Output: 28
Line Notes
const memo = Array.from({ length: m }, () => Array(n).fill(-1));Create 2D memo array initialized to -1.
if (memo[i][j] !== -1) return memo[i][j];Return cached value if computed.
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1);Cache the sum of paths from down and right.
return memo[i][j];Return memoized result.
TimeO(m*n)
SpaceO(m*n) for memo table and recursion stack
Each cell is computed once and cached, reducing exponential calls to polynomial.
💡 For m=n=10, about 100 computations instead of millions.
Interview Verdict: Accepted
Memoization makes the solution efficient and practical for interview constraints.