LeetCode 63: Unique Paths II Solution in Python Explained

Problem Statement

Section link icon

LeetCode 63, Unique Paths II, is a medium-level problem where you’re given an (m \times n) grid represented as a 2D array obstacleGrid. Each cell is either 0 (empty) or 1 (obstacle). Your task is to find the number of unique paths from the top-left corner (0,0) to the bottom-right corner (m-1,n-1), moving only right or down, avoiding obstacles. Return the total number of possible paths as an integer.

Constraints

  • 1 <= m, n <= 100: Grid dimensions are between 1 and 100.
  • obstacleGrid[i][j] is 0 or 1: Cells are either empty or obstacles.

Example

Input: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
Output: 2
Explanation: 3x3 grid with obstacle at (1,1):
Paths: 
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right
Total = 2

Input: obstacleGrid = [[0,1],[0,0]]
Output: 1
Explanation: 2x2 grid with obstacle at (0,1):
Only path: Down -> Right

Input: obstacleGrid = [[1]]
Output: 0
Explanation: 1x1 grid with obstacle, no paths possible.

Understanding the Problem

Section link icon

How do you solve LeetCode 63: Unique Paths II in Python? For obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]], count unique paths from (0,0) to (2,2), avoiding the obstacle at (1,1)—here, there are 2 paths. Like LeetCode 62, we move right or down, but obstacles block certain cells. We’ll explore two approaches: a Dynamic Programming Solution (optimal and primary) and an Alternative with Space-Optimized DP (more efficient in space). The DP method runs in O(m * n) time with O(m * n) space, adapting to obstacles.

Solution 1: Dynamic Programming Approach (Primary)

Section link icon

Explanation

Use a 2D DP table where dp[i][j] represents the number of unique paths to reach position (i,j) from (0,0). If a cell has an obstacle, set dp[i][j] = 0. Otherwise, dp[i][j] = dp[i-1][j] + dp[i][j-1] (sum of paths from above and left). Initialize the first row and column based on obstacles, then fill the table.

Here’s a flow for [[0,0,0],[0,1,0],[0,0,0]]:

DP table:
[1,1,1]    [1,1,1]
[1,0,0] -> [1,0,1]
[1,0,0]    [1,1,2]
Result: dp[2][2] = 2
  1. Initialize DP Table.
  • Create \(m \times n\) table, set first cell based on (0,0).
  1. Fill First Row and Column.
  • Propagate 1s until an obstacle is hit.
  1. Fill Table.
  • Sum paths from above and left, 0 if obstacle.
  1. Return Result.
  • Value at dp[m-1][n-1].

Step-by-Step Example

Example 1: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]

We need 2.

  • Goal: Count unique paths in 3x3 grid with obstacle.
  • Result for Reference: 2.
  • Start: m = 3, n = 3, dp = [[0,0,0],[0,0,0],[0,0,0]].
  • Step 1: Initialize first cell.
    • obstacleGrid[0][0] = 0, dp[0][0] = 1.
  • Step 2: Fill first row.
    • dp[0][1] = 1 (no obstacle), dp[0][2] = 1, dp[0] = [1,1,1].
  • Step 3: Fill first column.
    • dp[1][0] = 1, dp[2][0] = 1, dp = [[1,1,1],[1,0,0],[1,0,0]].
  • Step 4: Fill table.
    • dp[1][1]: obstacle = 1, dp[1][1] = 0.
    • dp[1][2] = dp[1][1] + dp[0][2] = 0 + 1 = 1.
    • dp[2][1] = dp[2][0] + dp[1][1] = 1 + 0 = 1.
    • dp[2][2] = dp[2][1] + dp[1][2] = 1 + 1 = 2.
    • dp = [[1,1,1],[1,0,1],[1,1,2]].
  • Finish: Return dp[2][2] = 2.

Example 2: obstacleGrid = [[0,1],[0,0]]

We need 1.

  • Start: dp = [[0,0],[0,0]].
  • Step 1: dp[0][0] = 1.
  • Step 2: First row: dp[0][1] = 0 (obstacle).
  • Step 3: First column: dp[1][0] = 1.
  • Step 4: dp[1][1] = dp[1][0] + dp[0][1] = 1 + 0 = 1.
    • dp = [[1,0],[1,1]].
  • Finish: Return dp[1][1] = 1.

How the Code Works (Dynamic Programming)

Here’s the Python code with line-by-line explanation:

def uniquePathsWithObstacles(obstacleGrid: list[list[int]]) -> int:
    # Line 1: Handle edge case (start is obstacle)
    if obstacleGrid[0][0] == 1:
        return 0

    # Line 2: Initialize DP table
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    dp = [[0] * n for _ in range(m)]
    dp[0][0] = 1

    # Line 3: Fill first row
    for j in range(1, n):
        if obstacleGrid[0][j] == 0:
            dp[0][j] = dp[0][j-1]

    # Line 4: Fill first column
    for i in range(1, m):
        if obstacleGrid[i][0] == 0:
            dp[i][0] = dp[i-1][0]

    # Line 5: Fill rest of table
    for i in range(1, m):
        for j in range(1, n):
            if obstacleGrid[i][j] == 0:
                dp[i][j] = dp[i-1][j] + dp[i][j-1]

    # Line 6: Return number of unique paths
    return dp[m-1][n-1]

Alternative: Space-Optimized DP Approach

Section link icon

Explanation

Optimize space by using a 1D DP array, updating it row by row. Each cell dp[j] represents paths to (i,j), and we only need the current row and previous row’s values, reducing space to O(n).

  1. Initialize 1D DP.
  • Size \(n\), set based on first row.
  1. Process Rows.
  • Update array for each row, considering obstacles.
  1. Return Result.
  • Last element of array.

Step-by-Step Example (Alternative)

For [[0,0,0],[0,1,0],[0,0,0]]:

  • Start: dp = [0,0,0].
  • Step 1: First row: dp = [1,1,1].
  • Step 2: Second row.
    • dp[0] = 1, dp[1] = 0 (obstacle), dp[2] = 1 (1+0).
  • Step 3: Third row.
    • dp[0] = 1, dp[1] = 1 (1+0), dp[2] = 2 (1+1).
  • Finish: Return dp[2] = 2.

How the Code Works (Space-Optimized DP)

def uniquePathsWithObstaclesOpt(obstacleGrid: list[list[int]]) -> int:
    # Line 1: Handle edge case
    if obstacleGrid[0][0] == 1:
        return 0

    # Line 2: Initialize 1D DP array
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    dp = [0] * n
    dp[0] = 1

    # Line 3: Process each row
    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]

    # Line 4: Return number of unique paths
    return dp[n-1]

Complexity

  • Dynamic Programming:
    • Time: O(m * n) – Fill \(m \times n\) table.
    • Space: O(m * n) – Store DP table.
  • Space-Optimized DP:
    • Time: O(m * n) – Process each cell.
    • Space: O(n) – 1D array.

Efficiency Notes

Space-Optimized DP is O(m * n) time and O(n) space, more efficient than the full DP’s O(m * n) space, making it a strong choice for LeetCode 63. Both are O(m * n) time, suitable for the problem size.

Key Insights

Tips to master LeetCode 63:

  • Obstacle Impact: Set 0 for blocked cells.
  • DP Flexibility: Adapt LeetCode 62 with obstacles.
  • Space Saving: Use 1D array for efficiency.

Additional Example

Section link icon

For obstacleGrid = [[0,0],[1,0]]:

  • Goal: 0.
  • DP: dp = [[1,1],[0,0]], no path to (1,1).
  • Result: 0.

Edge Cases

Section link icon
  • Single Cell: [[0]] – Return 1; [[1]] – Return 0.
  • All Obstacles: [[1,0]] – Return 0.
  • No Obstacles: Same as LeetCode 62.

Final Thoughts

Section link icon

The Dynamic Programming solution is a reliable pick for LeetCode 63 in Python—clear and adaptable, with the Space-Optimized version offering a memory boost. For a related challenge, try LeetCode 62: Unique Paths without obstacles! Ready to navigate? Solve LeetCode 63 on LeetCode now and test it with [[0,0,0],[0,1,0],[0,0,0]] or [[0,1],[0,0]] to master pathfinding with obstacles. Dive in and chart the course!