An m x n grid contains open cells marked 0 and obstacles marked 1. A robot starts at the top-left cell and must reach the bottom-right cell.
Only right and down movements are allowed. Return the number of distinct paths containing only open cells.
Example 1
Input: grid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
Output: 2
Explanation: The valid move sequences are Right -> Right -> Down -> Down and Down -> Down -> Right -> Right.
Example 2
Input: grid = [[1]]
Output: 0
Explanation: The starting cell is blocked, so no path can begin.
Recursion
Every path reaching an open cell must arrive from directly above or directly left. The two final-move groups never overlap, so adding both counts gives the total for the cell. A boundary crossing or obstacle contributes no valid route.
Recursion applies the same smaller path-counting task at every position. The state solve(row, col) represents the number of valid paths from the start to (row, col). The public method begins at the destination because the destination state represents the complete grid journey, and recursive calls walk backward toward the start.
Algorithm
Define
solve(row, col)as the number of valid paths from the start to a selected cell, so every recursive state has one clear meaning.Return
0after a negative row or column appears because a position outside the grid cannot belong to a valid route.Return
0for an obstacle because no path may enter a blocked cell or continue through a blocked state.Return
1at the open starting cell because staying at the start forms the single path prefix needed by later states.Request the path counts from above and left because every legal final move into the selected cell comes from exactly one of the two positions.
Add both recursive answers because the final move separates all valid paths into two disjoint groups.
Start recursion at the bottom-right cell because the destination state contains the answer for the entire grid.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Counts valid paths ending at a selected cell. int solve(int row, int col, vector<vector<int>>& grid) { // Rejects positions outside the grid. if (row < 0 || col < 0) { return 0; } // Rejects paths entering an obstacle. if (grid[row][col] == 1) { return 0; } // Counts the open starting cell as one path. if (row == 0 && col == 0) { return 1; } // Counts paths arriving from the cell above. int fromAbove = solve(row - 1, col, grid); // Counts paths arriving from the cell on the left. int fromLeft = solve(row, col - 1, grid); // Adds disjoint final-move groups. return fromAbove + fromLeft; }public: // Returns the number of obstacle-free paths. int uniquePathsWithObstacles(vector<vector<int>>& grid) { int rows = grid.size(); int cols = grid[0].size(); // Starts from the destination for the full grid. return solve(rows - 1, cols - 1, grid); }};// Driver codeint main() { vector<vector<int>> grid = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} }; Solution obj; cout << obj.uniquePathsWithObstacles(grid); return 0;}Note: Direct recursion may fail for large input values. Repeated subproblems create exponential work, so an online judge may report Time Limit Exceeded.
Complexity Analysis
Time Complexity: O(2(M + N)), where M is the number of rows and N is the number of columns, because each recursive level can branch into at most two choices across a path of length up to M + N - 2.
Space Complexity: O(M + N), because the recursion stack can contain at most one active call for each row or column move along a path.
Memoization
Direct recursion reaches the same cell through many different move sequences. Every repeated call asks for the same path count, so recalculation adds avoidable work.
A 2D array named dp stores the answer for every state (row, col). A stored value is returned immediately during later visits. The recursive state and recurrence remain unchanged.
Algorithm
Fill a grid-shaped
dpmatrix with-1so every untouched entry clearly marks an uncalculated path count.Keep
solve(row, col)identical to the recursive state so memoization changes only repeated work, not the path-count meaning.Return
0for a boundary crossing or obstacle because neither invalid state can finish a valid path prefix.Return
1at the open starting cell because every later count must grow from one valid starting prefix.Reuse
dp[row][col]whenever a stored answer exists because every visit to the same cell asks for the same path count.Add the answers from above and left, then store the sum in
dp[row][col]so later branches avoid recalculation.Begin at the destination and return the saved destination count because the destination state represents the complete problem.
Dry Run
Diagram 1
Solution
#include <bits/stdc++.h>using namespace std;class Solution {private: // Counts paths and stores completed states. int solve(int row, int col, vector<vector<int>>& grid, vector<vector<int>>& dp) { // Rejects positions outside the grid. if (row < 0 || col < 0) { return 0; } // Rejects paths entering an obstacle. if (grid[row][col] == 1) { return 0; } // Counts the open starting cell as one path. if (row == 0 && col == 0) { return 1; } // Reuses a previously calculated path count. if (dp[row][col] != -1) { return dp[row][col]; } // Counts paths arriving from the cell above. int fromAbove = solve(row - 1, col, grid, dp); // Counts paths arriving from the cell on the left. int fromLeft = solve(row, col - 1, grid, dp); // Stores the sum to avoid repeated work. dp[row][col] = fromAbove + fromLeft; return dp[row][col]; }public: // Returns the number of obstacle-free paths. int uniquePathsWithObstacles(vector<vector<int>>& grid) { int rows = grid.size(); int cols = grid[0].size(); vector<vector<int>> dp(rows, vector<int>(cols, -1)); // Starts from the destination for the full grid. return solve(rows - 1, cols - 1, grid, dp); }};// Driver codeint main() { vector<vector<int>> grid = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} }; Solution obj; cout << obj.uniquePathsWithObstacles(grid); return 0;}Complexity Analysis
Time Complexity: O(M × N), where M is the number of rows and N is the number of columns, because each grid state is computed at most once with constant work.
Space Complexity: O(M × N), because the dp matrix stores M × N values, while the recursion stack adds at most O(M + N) space.
Tabulation
Memoization still depends on recursive calls. Tabulation evaluates the same states directly in row-major order, removing recursion while preserving the recurrence.
For an open cell, the value above has already been calculated in the previous row, and the value left has already been calculated in the current row. An obstacle receives 0, preventing blocked routes from contributing to later cells.
Algorithm
Initialize a grid-shaped
dpmatrix with0so blocked cells and unavailable predecessors naturally contribute no paths.Process cells from top to bottom and left to right because every required state above or left must be ready before use.
Keep
0at every obstacle because blocked cells cannot receive a path or pass a count to a later cell.Store
1at the open starting cell because one valid path prefix begins before any movement occurs.Read the value above only below the top edge, using
0on the edge because no valid predecessor exists outside the grid.Read the value left only beyond the first column, again using
0because no path can enter from outside the grid.Add both predecessor counts for every other open cell and return
dp[m - 1][n - 1]because the destination entry collects every valid route.
Dry Run
Unique Path II Tabulation
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns the path count through tabulation. int uniquePathsWithObstacles(vector<vector<int>>& grid) { int rows = grid.size(); int cols = grid[0].size(); vector<vector<int>> dp(rows, vector<int>(cols, 0)); // Processes cells after predecessor states are ready. for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { // Keeps every blocked state unreachable. if (grid[row][col] == 1) { dp[row][col] = 0; continue; } // Initializes the only path at the starting cell. if (row == 0 && col == 0) { dp[row][col] = 1; continue; } int fromAbove = 0; int fromLeft = 0; // Reads the predecessor above only inside the grid. if (row > 0) { fromAbove = dp[row - 1][col]; } // Reads the predecessor left only inside the grid. if (col > 0) { fromLeft = dp[row][col - 1]; } // Adds both valid predecessor groups. dp[row][col] = fromAbove + fromLeft; } } return dp[rows - 1][cols - 1]; }};// Driver codeint main() { vector<vector<int>> grid = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} }; Solution obj; cout << obj.uniquePathsWithObstacles(grid); return 0;}Complexity Analysis
Time Complexity: O(M × N), where M is the number of rows and N is the number of columns, because each grid cell is processed once with constant work.
Space Complexity: O(M × N), because the dp matrix stores one path count for every grid cell and no recursion stack is used.
Space Optimization
Tabulation uses only the value above from the previous row and the value left from the current row. Older rows never contribute again, so a full matrix is unnecessary.
A one-dimensional dp array stores path counts for the active row. Before an update, dp[col] represents the value above, while dp[col - 1] represents the value left. The predecessor sum forms current, and assigning current to dp[col] shifts the column state to the active row.
Algorithm
Initialize a one-dimensional
dparray with0so every column starts unreachable before row processing begins.Process each row from left to right because
dp[col]must still hold the value above whiledp[col - 1]already holds the current-row value from left.Reset
dp[col]to0at an obstacle because no blocked state may survive and contribute to later cells.Store
1at the open starting cell because the first path prefix exists before any move is taken.Read
dp[col]asfromAboveand the preceding entry asfromLeftbecause the one-dimensional array preserves both required predecessor states.Calculate
currentby adding both predecessor counts because every path reaches the active cell from exactly one direction.Shift
currentintodp[col]and return the last entry because completed updates leave the destination count at the final column.
Dry Run
Unique Path II Space Optimization
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Returns the number of paths with one-dimensional storage. int uniquePathsWithObstacles(vector<vector<int>>& grid) { int rows = grid.size(); int cols = grid[0].size(); vector<int> dp(cols, 0); // Processes rows while reusing the same column states. for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { // Clears every blocked column state. if (grid[row][col] == 1) { dp[col] = 0; continue; } // Initializes the only path at the starting cell. if (row == 0 && col == 0) { dp[col] = 1; continue; } int fromAbove = dp[col]; int fromLeft = 0; // Reads the active-row value from the left. if (col > 0) { fromLeft = dp[col - 1]; } // Calculates current from above and left. int current = fromAbove + fromLeft; // Shifts the column state to the active row. dp[col] = current; } } return dp[cols - 1]; }};// Driver codeint main() { vector<vector<int>> grid = { {0, 0, 0}, {0, 1, 0}, {0, 0, 0} }; Solution obj; cout << obj.uniquePathsWithObstacles(grid); return 0;}Complexity Analysis
Time Complexity: O(M × N), where M is the number of rows and N is the number of columns, because each grid cell is processed once with constant work.
Space Complexity: O(N), because one path count is stored for each column while values from older rows are discarded.
Interview follow-up Questions
The answer becomes 0. A blocked starting cell prevents any path from beginning, while a blocked destination prevents any path from finishing.
Be the first to add a comment.