💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency while preserving the recursive intuition.
Intuition
Use recursion as before but store results for each cell so repeated calls return cached answers instead of recomputing.
Algorithm
- Initialize a memo table to store minimum path sums for each cell.
- Recursively compute minimum path sums as before.
- Before computing a cell, check if its result is already cached in memo.
- Return cached result if available; otherwise compute, cache, and return.
💡 Memoization transforms exponential recursion into polynomial time by pruning duplicate calls.
Recurrence:f(i,j) = grid[i][j] + min(f(i+1,j), f(i,j+1)) with memoization
def minPathSum(grid):
m, n = len(grid), len(grid[0])
memo = [[-1]*n for _ in range(m)]
def dfs(i, j):
if i == m - 1 and j == n - 1:
return grid[i][j]
if i >= m or j >= n:
return float('inf')
if memo[i][j] != -1:
return memo[i][j]
right = dfs(i, j + 1)
down = dfs(i + 1, j)
memo[i][j] = grid[i][j] + min(right, down)
return memo[i][j]
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(minPathSum(grid)) # Output: 7
Line Notes
memo = [[-1]*n for _ in range(m)]Initialize memo table with -1 indicating uncomputed cells
if memo[i][j] != -1:Return cached result if already computed
memo[i][j] = grid[i][j] + min(right, down)Store computed minimum path sum for reuse
return memo[i][j]Return memoized result to avoid recomputation
public class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] memo = new int[m][n];
for (int[] row : memo) Arrays.fill(row, -1);
return dfs(grid, 0, 0, memo);
}
private int dfs(int[][] grid, int i, int j, int[][] memo) {
int m = grid.length, n = grid[0].length;
if (i == m - 1 && j == n - 1) return grid[i][j];
if (i >= m || j >= n) return Integer.MAX_VALUE;
if (memo[i][j] != -1) return memo[i][j];
int right = dfs(grid, i, j + 1, memo);
int down = dfs(grid, i + 1, j, memo);
memo[i][j] = grid[i][j] + Math.min(right, down);
return memo[i][j];
}
public static void main(String[] args) {
Solution sol = new Solution();
int[][] grid = {{1,3,1},{1,5,1},{4,2,1}};
System.out.println(sol.minPathSum(grid)); // 7
}
}
Line Notes
int[][] memo = new int[m][n];Create memo table to cache results
for (int[] row : memo) Arrays.fill(row, -1);Initialize memo with -1 to mark unvisited cells
if (memo[i][j] != -1) return memo[i][j];Return cached result if available
memo[i][j] = grid[i][j] + Math.min(right, down);Store computed minimum path sum
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>> memo(m, vector<int>(n, -1));
return dfs(grid, 0, 0, memo);
}
private:
int dfs(vector<vector<int>>& grid, int i, int j, vector<vector<int>>& memo) {
int m = grid.size(), n = grid[0].size();
if (i == m - 1 && j == n - 1) return grid[i][j];
if (i >= m || j >= n) return INT_MAX;
if (memo[i][j] != -1) return memo[i][j];
int right = dfs(grid, i, j + 1, memo);
int down = dfs(grid, i + 1, j, memo);
memo[i][j] = grid[i][j] + min(right, down);
return memo[i][j];
}
};
int main() {
Solution sol;
vector<vector<int>> grid = {{1,3,1},{1,5,1},{4,2,1}};
cout << sol.minPathSum(grid) << endl; // 7
return 0;
}
Line Notes
vector<vector<int>> memo(m, vector<int>(n, -1));Initialize memo table with -1 for uncomputed states
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = grid[i][j] + min(right, down);Store computed minimum path sum
return memo[i][j];Return memoized result
var minPathSum = function(grid) {
const m = grid.length, n = grid[0].length;
const memo = Array.from({length: m}, () => Array(n).fill(-1));
function dfs(i, j) {
if (i === m - 1 && j === n - 1) return grid[i][j];
if (i >= m || j >= n) return Infinity;
if (memo[i][j] !== -1) return memo[i][j];
const right = dfs(i, j + 1);
const down = dfs(i + 1, j);
memo[i][j] = grid[i][j] + Math.min(right, down);
return memo[i][j];
}
return dfs(0, 0);
};
// Example usage
console.log(minPathSum([[1,3,1],[1,5,1],[4,2,1]])); // 7
Line Notes
const memo = Array.from({length: m}, () => Array(n).fill(-1));Create memo table initialized with -1
if (memo[i][j] !== -1) return memo[i][j];Return cached result if computed
memo[i][j] = grid[i][j] + Math.min(right, down);Cache computed minimum path sum
return memo[i][j];Return memoized value
TimeO(m*n)
SpaceO(m*n) for memo and recursion stack
Each cell is computed once and cached, reducing exponential calls to polynomial time.
💡 For a 200x200 grid, this means 40,000 computations, which is efficient and feasible.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for interview constraints and is a common optimization step.