🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing paths for the same cell, drastically improving efficiency.
Intuition
Store results of subproblems (paths from each cell) so repeated calls return cached results.
Algorithm
- Initialize a memo table to store results for each cell.
- Recursively compute paths from (0,0), returning cached results if available.
- If cell is obstacle or out of bounds, return 0.
- If destination reached, return 1.
- Sum memoized results from down and right neighbors.
💡 Memoization turns exponential recursion into polynomial by avoiding duplicate work.
Recurrence:f(i,j) = 0 if obstacle or out of bounds; 1 if destination; else memo[i][j] = f(i+1,j) + f(i,j+1)
def uniquePathsWithObstacles(obstacleGrid):
m, n = len(obstacleGrid), len(obstacleGrid[0])
memo = [[-1]*n for _ in range(m)]
def dfs(i, j):
if i >= m or j >= n or obstacleGrid[i][j] == 1:
return 0
if i == m - 1 and j == n - 1:
return 1
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__':
grid = [[0,0,0],[0,1,0],[0,0,0]]
print(uniquePathsWithObstacles(grid)) # Output: 2
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] = dfs(i + 1, j) + dfs(i, j + 1)Store computed paths count for reuse
return memo[i][j]Return memoized result
public class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length, n = obstacleGrid[0].length;
int[][] memo = new int[m][n];
for (int[] row : memo) Arrays.fill(row, -1);
return dfs(obstacleGrid, 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 || j >= n || grid[i][j] == 1) return 0;
if (i == m - 1 && j == n - 1) return 1;
if (memo[i][j] != -1) return memo[i][j];
memo[i][j] = dfs(grid, i + 1, j, memo) + dfs(grid, i, j + 1, memo);
return memo[i][j];
}
public static void main(String[] args) {
Solution sol = new Solution();
int[][] grid = {{0,0,0},{0,1,0},{0,0,0}};
System.out.println(sol.uniquePathsWithObstacles(grid)); // 2
}
}
Line Notes
int[][] memo = new int[m][n];Create memo table for caching
for (int[] row : memo) Arrays.fill(row, -1);Initialize memo with -1 to mark unvisited
if (memo[i][j] != -1) return memo[i][j];Return cached result if available
memo[i][j] = dfs(grid, i + 1, j, memo) + dfs(grid, i, j + 1, memo);Store computed paths count
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int uniquePathsWithObstacles(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 || j >= n || grid[i][j] == 1) return 0;
if (i == m - 1 && j == n - 1) return 1;
if (memo[i][j] != -1) return memo[i][j];
memo[i][j] = dfs(grid, i + 1, j, memo) + dfs(grid, i, j + 1, memo);
return memo[i][j];
}
};
int main() {
Solution sol;
vector<vector<int>> grid = {{0,0,0},{0,1,0},{0,0,0}};
cout << sol.uniquePathsWithObstacles(grid) << endl; // 2
return 0;
}
Line Notes
vector<vector<int>> memo(m, vector<int>(n, -1));Initialize memo table with -1
if (memo[i][j] != -1) return memo[i][j];Return cached result if computed
memo[i][j] = dfs(grid, i + 1, j, memo) + dfs(grid, i, j + 1, memo);Store computed paths count
return memo[i][j];Return memoized value
var uniquePathsWithObstacles = function(obstacleGrid) {
const m = obstacleGrid.length, n = obstacleGrid[0].length;
const memo = Array.from({length: m}, () => Array(n).fill(-1));
function dfs(i, j) {
if (i >= m || j >= n || obstacleGrid[i][j] === 1) return 0;
if (i === m - 1 && j === n - 1) return 1;
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:
console.log(uniquePathsWithObstacles([[0,0,0],[0,1,0],[0,0,0]])); // 2
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 available
memo[i][j] = dfs(i + 1, j) + dfs(i, j + 1);Store computed paths count
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.
💡 For a 100x100 grid, this means 10,000 computations, which is efficient.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for interview constraints.