Start learning free

LESSON 327 OF 348 · DYNAMIC PROGRAMMING

Unique Paths With Obstacles in TypeScript: 2D Dynamic Programming

Practice O(n * m) Read the full explanation

A robot is located at the top-left corner of a n x m grid (marked 'Start' in the diagram below).

The robot can only move either down or right at any moment of time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).

Now consider obstacles are placed in some of the grid's cells. How many unique paths would there be?

An obstacle and empty space is marked as 1 and 0 respectively in the grid.

Note: n and m will be at most 100.

It is guaranteed that the answer can be represented as a 32-bit integer.

Example 1:

Input:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right
STEP 1 OF 13
    Ctrl + Enter to check

    TYPESCRIPT · DYNAMIC PROGRAMMING

    Unique Paths in TypeScript, explained

    Problem overview

    The method uniquePaths(grid) receives an n by m grid of 0s and 1s. A robot starts at the top-left cell and wants to reach the bottom-right cell, moving only down or right. A 0 is an empty cell and a 1 is an obstacle the robot can't enter. The method returns the number of different paths.

    In the example the grid is 3 by 3 with one obstacle in the middle. The two paths are right, right, down, down and down, down, right, right. Every other route would have to cross the center cell, and the obstacle blocks it. The notes limit both dimensions to 100 and promise that the answer fits in a 32-bit integer.

    Approach: count the ways into each cell

    Enumerating paths one at a time gets out of hand quickly: an open 18 by 18 grid already has over two billion of them. The better idea is to count paths cell by cell.

    Let dp[i][j] be the number of paths from the start to cell (i, j). The robot can enter a cell only from the cell above it or the cell to its left, so for a free cell:

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

    An obstacle cell has no paths through it, so it stays at 0. This also does the right thing for its neighbors: they simply receive 0 from that direction.

    The first row and the first column have only one neighbor, so they are set up separately. Every cell in the first column can be reached in exactly one way, straight down, unless an obstacle appears above it. Once the robot's only route is cut, every cell below is unreachable and stays 0. The first row works the same way going right.

    For the example grid, the table comes out as:

    1 1 1
    1 0 1
    1 1 2
    

    The bottom-right value, 2, is the answer.

    Step by step

    1. Read the dimensions. n is grid.length (rows) and m is grid[0].length (columns).
    2. Create the table of n rows with m zeros each.
    3. Fill the first column. Go down from row 0. At the first obstacle, stop; otherwise set the cell to 1.
    4. Fill the first row. Go right from column 0 with the same rule. The start cell is written by both loops, which is harmless.
    5. Fill the rest row by row, starting at row 1 and column 1, so the cells above and to the left are always ready.
    6. Skip obstacles. Only compute a cell when grid[i][j] is 0.
    7. Apply the recurrence for free cells.
    8. Return the bottom-right cell. If it is an obstacle, it is still 0, which is the right answer.

    The solution in TypeScript

    class Solution {
        uniquePaths(grid: number[][]): number {
            let n = grid.length;
            let m = grid[0].length;
            let dp: number[][] = Array.from({ length: n }, () => new Array(m).fill(0));
            for (let i = 0; i < n; ++i) {
                if (grid[i][0] == 1) {
                    break;
                }
                dp[i][0] = 1;
            }
            for (let j = 0; j < m; ++j) {
                if (grid[0][j] == 1) {
                    break;
                }
                dp[0][j] = 1;
            }
            for (let i = 1; i < n; ++i) {
                for (let j = 1; j < m; ++j) {
                    if (grid[i][j] == 0) {
                        dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                    }
                }
            }
            return dp[n - 1][m - 1];
        }
    }
    

    TypeScript details worth knowing:

    • Building a 2D array safely. Array.from({ length: n }, () => new Array(m).fill(0)) calls the arrow function once per row, so each row is a different array. The tempting new Array(n).fill(new Array(m).fill(0)) stores one row object in all n slots. Setting bad[0][0] = 1 on such a table with two rows prints [ [ 1, 0 ], [ 1, 0 ] ]: both rows changed.
    • fill(0) is needed. A bare new Array(m) holds empty slots, and adding to one gives NaN.
    • == versus ===. The lesson compares with ==, which converts types when they differ. Here both sides are numbers, so it behaves like ===, which is the safer default.
    • break leaves the nearest loop. It ends only the loop it is in. The cells it skips keep their initial 0.
    • No overflow. A number is a double. The count stays exact up to 2^53, well beyond the 32-bit limit the notes mention. An open 18 by 18 grid gives 2333606220, which wouldn't fit in a 32-bit integer but is exact here.

    Complexity

    Time: O(n * m). Each cell is computed once with a constant amount of work.

    Space: O(n * m). The table is the same size as the grid. Because each row needs only the row above, a single row would be enough.

    Edge cases and common mistakes

    • Blocked start. If grid[0][0] is 1, both setup loops break at once. Nothing is reachable and the answer is 0. This works because the recurrence then adds only zeros.
    • Blocked finish. The last cell is skipped by the == 0 test and stays 0.
    • A blocked first column. [[0], [1], [0]] returns 0: the middle obstacle cuts off the last cell.
    • Single cell. [[0]] returns 1, the empty path, and [[1]] returns 0.
    • Single row. [[0, 0, 0, 0]] returns 1.
    • Not breaking on an obstacle. Without break, the cells after the obstacle in the first row would still get a 1, as if the robot could jump over it.
    • Empty grid. grid[0].length throws a TypeError when grid is [], since grid[0] is undefined. The lesson doesn't ask for it.

    Testing

    • [[0,0,0],[0,1,0],[0,0,0]] gives 2.
    • [[0,0],[0,0]] gives 2.
    • [[0,1],[1,0]] gives 0.
    • [[1,0],[0,0]] gives 0.
    • [[0],[1],[0]] gives 0.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free