Unique Paths II

Medium

๐Ÿ’ญ Introduction

Youโ€™re given an m x n grid where a robot starts at the top-left corner and wants to reach the bottom-right corner. The robot can only move right or down at any point in time.

But hereโ€™s the twist: some cells contain obstacles that the robot cannot pass through!

Your task is to find the number of unique paths the robot can take to reach the destination while avoiding obstacles.

๐Ÿงฉ Problem Statement

LeetCode #63 โ€” Unique Paths II

You are given an m x n integer array grid. There is a robot initially located at the top-left corner (i.e., grid[0][0]). The robot tries to move to the bottom-right corner (i.e., grid[m - 1][n - 1]). The robot can only move either down or right at any point in time.

An obstacle and space are marked as 1 or 0 respectively in grid. A path that the robot takes cannot include any square that is an obstacle.

Return the number of possible unique paths that the robot can take to reach the bottom-right corner.

๐Ÿ” Example 1:

Input: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
Output: 2

Explanation:

Start โ†’ [0,0,0]
        [0,X,0]  (X = obstacle)
   End โ†’ [0,0,0]

There are 2 ways to reach the bottom-right corner:

  1. Right โ†’ Right โ†’ Down โ†’ Down
  2. Down โ†’ Down โ†’ Right โ†’ Right

๐Ÿ” Example 2:

Input: obstacleGrid = [[0,1],[0,0]]
Output: 1

Explanation:

Start โ†’ [0,X]
   End โ†’ [0,0]

There is only 1 way: Down โ†’ Right

๐Ÿ” Example 3:

Input: obstacleGrid = [[1]]
Output: 0

Explanation:

The starting position itself has an obstacle, so there are 0 paths.

๐Ÿง  Step 1: Understanding the Problem

Letโ€™s break down the key points:

Movement Rules:

  • Can only move right or down
  • Cannot move through obstacles (cells with value 1)
  • Must reach from top-left to bottom-right

Key Observations:

  1. If starting position has an obstacle โ†’ return 0
  2. If ending position has an obstacle โ†’ return 0
  3. For any cell, paths = paths from above + paths from left (if no obstacle)
  4. If a cell has an obstacle โ†’ paths through it = 0

Relation to Unique Paths I: This is an extension of the classic Unique Paths problem, but with obstacles that block certain paths.

๐Ÿค” Step 2: The Recursive Approach

Letโ€™s think recursively:

paths(i, j) = 0                                    if grid[i][j] == 1 (obstacle)
            = 1                                    if i == 0 and j == 0 (start)
            = paths(i-1, j) + paths(i, j-1)       otherwise
def uniquePathsWithObstacles(obstacleGrid):
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    
    def helper(i, j):
        # Out of bounds
        if i < 0 or j < 0:
            return 0
        
        # Obstacle
        if obstacleGrid[i][j] == 1:
            return 0
        
        # Starting position
        if i == 0 and j == 0:
            return 1
        
        # Sum paths from top and left
        return helper(i - 1, j) + helper(i, j - 1)
    
    return helper(m - 1, n - 1)

Problem: This has exponential time complexity due to overlapping subproblems!

โฑ๏ธ Time Complexity: $O(2^{m+n})$

๐Ÿ“ฆ Space Complexity: $O(m + n)$ for recursion stack

๐Ÿš€ Step 3: Top-Down DP (Memoization)

We can cache results to avoid recomputation:

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        
        # Edge case: start or end has obstacle
        if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
            return 0
        
        memo = {}
        
        def dp(i, j):
            # Out of bounds
            if i < 0 or j < 0:
                return 0
            
            # Obstacle
            if obstacleGrid[i][j] == 1:
                return 0
            
            # Starting position
            if i == 0 and j == 0:
                return 1
            
            # Check memo
            if (i, j) in memo:
                return memo[(i, j)]
            
            # Calculate paths
            memo[(i, j)] = dp(i - 1, j) + dp(i, j - 1)
            return memo[(i, j)]
        
        return dp(m - 1, n - 1)

โฑ๏ธ Time Complexity: $O(m \times n)$

๐Ÿ“ฆ Space Complexity: $O(m \times n)$ for memoization

โš™๏ธ Step 4: Bottom-Up DP (Tabulation)

Letโ€™s build a table from the ground up:

Define: dp[i][j] = number of unique paths to reach cell (i, j)

Recurrence relation:

dp[i][j] = 0                           if obstacleGrid[i][j] == 1
         = dp[i-1][j] + dp[i][j-1]     otherwise

Base case:

dp[0][0] = 1 if obstacleGrid[0][0] == 0, else 0
class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        
        # Edge case: start or end has obstacle
        if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
            return 0
        
        # Create DP table
        dp = [[0] * n for _ in range(m)]
        dp[0][0] = 1
        
        # Fill first row
        for j in range(1, n):
            if obstacleGrid[0][j] == 0:
                dp[0][j] = dp[0][j-1]
            else:
                dp[0][j] = 0
        
        # Fill first column
        for i in range(1, m):
            if obstacleGrid[i][0] == 0:
                dp[i][0] = dp[i-1][0]
            else:
                dp[i][0] = 0
        
        # Fill rest of the table
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    dp[i][j] = 0
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        
        return dp[m-1][n-1]

Letโ€™s trace through Example 1: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]

i\j 0 1 2
0 1 1 1
1 1 0 1
2 1 1 2

Step-by-step:

  • dp[0][0] = 1 (start)
  • dp[0][1] = 1 (from left)
  • dp[0][2] = 1 (from left)
  • dp[1][0] = 1 (from above)
  • dp[1][1] = 0 (obstacle!)
  • dp[1][2] = 1 (only from above, since left is obstacle)
  • dp[2][0] = 1 (from above)
  • dp[2][1] = 1 (from above, since left is 1)
  • dp[2][2] = 2 (from above: 1, from left: 1)

Final answer: 2 โœ“

โฑ๏ธ Time Complexity: $O(m \times n)$

๐Ÿ“ฆ Space Complexity: $O(m \times n)$

๐Ÿงฎ Step 5: Space Optimization (1D DP)

We can optimize space by using only one row:

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        
        # Edge case: start or end has obstacle
        if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
            return 0
        
        # Use single row
        dp = [0] * n
        dp[0] = 1
        
        for i in range(m):
            for j in range(n):
                if obstacleGrid[i][j] == 1:
                    dp[j] = 0
                elif j > 0:
                    dp[j] += dp[j-1]
        
        return dp[n-1]

How it works:

  • dp[j] represents paths to current cell in current row
  • When we process a cell, dp[j] already contains paths from above (previous iteration)
  • We add dp[j-1] which contains paths from left (current iteration)

โฑ๏ธ Time Complexity: $O(m \times n)$

๐Ÿ“ฆ Space Complexity: $O(n)$ โ€” Optimal!

๐ŸŽฏ Key Insights

  1. Obstacle handling: Any cell with obstacle has 0 paths through it

  2. Edge cases matter: Check if start or end has obstacle before processing

  3. First row/column: Must be handled carefully since they only have one direction

  4. DP state: dp[i][j] = sum of paths from top and left (if no obstacle)

  5. Space optimization: Only need previous rowโ€™s values, so can use 1D array

๐Ÿ“Š Complexity Summary

Approach Time Space Notes
Naive Recursion $O(2^{m+n})$ $O(m+n)$ Too slow
Memoization $O(m \times n)$ $O(m \times n)$ Top-down DP
2D Tabulation $O(m \times n)$ $O(m \times n)$ Bottom-up DP
1D Tabulation $O(m \times n)$ $O(n)$ Space optimized

๐ŸŒŸ Edge Cases to Consider

  1. Start has obstacle: [[1]] โ†’ Return 0
  2. End has obstacle: [[0,0],[0,1]] โ†’ Return 0
  3. Single cell no obstacle: [[0]] โ†’ Return 1
  4. All obstacles: [[1,1],[1,1]] โ†’ Return 0
  5. No obstacles: Same as Unique Paths I
  6. Obstacle blocks all paths: [[0,1],[1,0]] โ†’ Return 0

๐Ÿ’ก Common Mistakes

  1. Forgetting to check start/end: Must verify no obstacle at start or end
  2. Wrong first row/column initialization: Obstacles break the chain
  3. Not handling obstacles in DP: Must set dp[i][j] = 0 for obstacles
  4. Off-by-one errors: Be careful with array indices
  5. Modifying input: Some solutions modify the grid, which may not be allowed

๐Ÿงช Step-by-Step Example

Letโ€™s trace through obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]:

Initial Grid:
[0, 0, 0]
[0, X, 0]  (X = obstacle)
[0, 0, 0]

DP Table Construction:
Step 1: Initialize first cell
[1, 0, 0]
[0, 0, 0]
[0, 0, 0]

Step 2: Fill first row (can only come from left)
[1, 1, 1]
[0, 0, 0]
[0, 0, 0]

Step 3: Fill first column (can only come from above)
[1, 1, 1]
[1, 0, 0]
[1, 0, 0]

Step 4: Fill remaining cells
[1, 1, 1]
[1, 0, 1]  โ† dp[1][2] = dp[0][2] + 0 (obstacle at left)
[1, 1, 2]  โ† dp[2][2] = dp[1][2] + dp[2][1] = 1 + 1 = 2

Answer: 2 paths

๐Ÿ” Variations of This Problem

  1. Unique Paths I โ€” Same problem without obstacles
  2. Minimum Path Sum โ€” Find path with minimum sum
  3. Unique Paths III โ€” Visit every non-obstacle cell exactly once
  4. Dungeon Game โ€” Similar grid DP with health constraints
  • House Robber - Linear DP pattern
  • Maximum Subarray - 1D DP optimization
  • Minimum Path Sum - Similar grid DP
  • Triangle - Similar path counting

๐Ÿ† Why This Problem Matters

  1. Grid DP foundation: Classic example of 2D DP
  2. Obstacle handling: Teaches conditional DP transitions
  3. Space optimization: Shows how to reduce from 2D to 1D
  4. Real-world applications: Pathfinding, robotics, game development
  5. Interview favorite: Common in coding interviews

๐ŸŽ“ Key Takeaways

  • Grid DP pattern: Build solution cell by cell, row by row
  • Obstacle = 0 paths: Any obstacle blocks all paths through it
  • Edge initialization: First row and column need special handling
  • Space optimization: Can reduce from O(mร—n) to O(n) space
  • Edge cases: Always check start and end positions for obstacles

๐Ÿ“ Interview Tips

  1. Clarify constraints: Ask about grid size, obstacle representation
  2. Check edge cases: Start with obstacles at start/end positions
  3. Explain approach: Start with 2D DP, then mention space optimization
  4. Trace example: Walk through a small example to verify logic
  5. Discuss complexity: Explain both time and space complexity