AlgoMaster Logo

Unique Paths II

Last Updated: March 28, 2026

medium
5 min read

Understanding the Problem

This is a direct extension of the classic "Unique Paths" problem, with one twist: some cells are blocked by obstacles. The robot still starts at the top-left and needs to reach the bottom-right, moving only right or down. But now, any cell containing a 1 is impassable, and no valid path can go through it.

The key question becomes: how do obstacles affect the number of reachable paths? In the original problem, every cell was reachable and contributed paths from its top and left neighbors. Here, an obstacle cell contributes zero paths. It's a dead end. Any path that would have gone through that cell simply doesn't exist.

There's also a subtle edge case to watch for: if the start cell (0, 0) or the destination cell (m-1, n-1) is itself an obstacle, the answer is immediately 0. No path can begin at a wall, and no path can end at one either.

Key Constraints:

  • 1 <= m, n <= 100 -> The grid has at most 10,000 cells. O(m n) is very comfortable, and even O(m n * log(something)) would be fine.
  • obstacleGrid[i][j] is 0 or 1 -> Binary values, so we don't need to worry about varying obstacle "weights" or partial blockages.
  • Answer fits in a 32-bit signed integer (up to 2 * 10^9).

Approach 1: Recursion (Brute Force)

Intuition

The most natural starting point is recursion. From any cell (i, j), the robot has two choices: go right to (i, j + 1) or go down to (i + 1, j). The total number of paths from (i, j) to the destination is the sum of paths from those two neighbors. If a cell is an obstacle, it returns 0 because no path can pass through it.

This approach correctly handles obstacles by simply treating them as dead ends in the recursion tree. But it has the same fundamental flaw as the brute force for Unique Paths: it recomputes the same subproblems an exponential number of times.

Algorithm

  1. If the start cell (0, 0) or the destination cell (m-1, n-1) is an obstacle, return 0.
  2. Define a recursive function countPaths(i, j) that returns the number of paths from (i, j) to the bottom-right corner.
  3. Base case: if i == m - 1 and j == n - 1, return 1.
  4. If i >= m, j >= n, or the cell is an obstacle, return 0.
  5. Return countPaths(i + 1, j) + countPaths(i, j + 1).
  6. Call countPaths(0, 0).

Example Walkthrough

Input:

0
1
2
0
0
0
0
1
0
1
0
2
0
0
0
obstacleGrid

The recursion explores all possible paths. From (0,0), it tries going right and going down. Whenever it hits the obstacle at (1,1), that branch returns 0. The two surviving paths are: Right -> Right -> Down -> Down, and Down -> Down -> Right -> Right. Result: 2.

Code

The recursion recomputes the same cells over and over. There are only m * n distinct subproblems, but we solve each one many times. What if we built the answer bottom-up, computing each cell exactly once?

Approach 2: Dynamic Programming (2D Table)

Intuition

Instead of recursing from the top-left and caching results, we can build the answer bottom-up with a 2D DP table. Define dp[i][j] as the number of unique paths from (0, 0) to cell (i, j). The recurrence is the same as the original Unique Paths problem:

dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

But with one critical modification: if obstacleGrid[i][j] == 1, then dp[i][j] = 0. No path can end at an obstacle, period.

The first row and first column need special attention. In the obstacle-free version, they're all 1s. But here, an obstacle in the first row blocks everything to its right (since the only way to reach those cells is by moving right from the start). Similarly, an obstacle in the first column blocks everything below it.

Algorithm

  1. If the start cell or the destination cell is an obstacle, return 0.
  2. Create a 2D array dp of size m x n, initialized to 0.
  3. Fill the first column: set dp[i][0] = 1 for each row i until you hit an obstacle. Once you encounter an obstacle, all remaining cells below it stay 0.
  4. Fill the first row: set dp[0][j] = 1 for each column j until you hit an obstacle. Once you encounter an obstacle, all remaining cells to the right stay 0.
  5. For each cell (i, j) from (1, 1) to (m-1, n-1): if obstacle, set 0; otherwise, set dp[i][j] = dp[i - 1][j] + dp[i][j - 1].
  6. Return dp[m - 1][n - 1].

Example Walkthrough

1Initialize: first row and first column all 1s (no obstacles block them)
0
1
2
0
1
1
1
1
1
0
0
2
1
0
0
1/6

Code

The time complexity is already optimal, but we're using O(m * n) space for the DP table. When filling row i, we only ever look at row i - 1. What if we used a single 1D array and updated it in-place as we process each row?

Approach 3: Dynamic Programming (Space Optimized)

Intuition

The 2D DP table has an interesting property: to compute dp[i][j], we only need dp[i-1][j] (directly above) and dp[i][j-1] (directly to the left). We never look back more than one row. So instead of a full 2D table, we can use a single 1D array of size n.

Here's the trick. We process the grid row by row. At the start of processing row i, our 1D array dp[j] holds the values from row i - 1. As we sweep left to right across the row, we update each dp[j] to become the value for row i:

  • dp[j] (before update) = value from dp[i-1][j] (the cell above)
  • dp[j-1] (already updated) = value from dp[i][j-1] (the cell to the left)

So dp[j] = dp[j] + dp[j-1] naturally combines the top and left contributions. When we hit an obstacle, we set dp[j] = 0.

Algorithm

  1. If the start cell or the destination cell is an obstacle, return 0.
  2. Create a 1D array dp of size n, initialized to 0.
  3. Initialize the first row: set dp[j] = 1 for each column j until you hit an obstacle.
  4. For each row i from 1 to m - 1:
    • If obstacleGrid[i][0] == 1, set dp[0] = 0. Otherwise, dp[0] retains its value.
    • For each column j from 1 to n - 1: if obstacle, set dp[j] = 0; otherwise, dp[j] = dp[j] + dp[j - 1].
  5. Return dp[n - 1].

Example Walkthrough

1Initialize dp (row 0): all 1s, no obstacles in first row
0
1
1
1
2
1
1/8

Code