Paths around obstacles
A grid holds 0 for an open cell and 1 for an obstacle. Moving only right or down, count the
paths from the top-left cell to the bottom-right cell that never enter an obstacle. If either corner is an obstacle, the
answer is 0.
Examples
| Input | Output | Why |
|---|---|---|
grid = [[0,0,0],[0,1,0],[0,0,0]] | 2 | Around the obstacle on either side. |
grid = [[0,1],[0,0]] | 1 | Down, then right. |
grid = [[1]] | 0 | The start is blocked. |
Constraints
- 1 ≤ rows, cols ≤ 100
- The answer fits in a 32-bit integer
Hints
Hint 1
ways[r][c] = ways[r − 1][c] + ways[r][c − 1] for an open cell, 0 for an obstacle.
Hint 2
The start has one way if it is open.
Hint 3
A single row: ways[c] += ways[c − 1], reset to 0 at obstacles.
Where people lose marks
- Initialising the whole first row and column to 1, which ignores obstacles part-way along them.
- Forgetting that a blocked start or end gives 0.
Dry run on [[0,0,0,0],[0,1,0,0],[0,0,0,0]]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 12
Base cases filled first.
-
Step 2 / 12
(0, 1): from above 0 + from the left 1.
-
Step 3 / 12
(0, 2): from above 0 + from the left 1.
-
Step 4 / 12
(0, 3): from above 0 + from the left 1.
-
Step 5 / 12
(1, 0): from above 1 + from the left 0.
-
Step 6 / 12
(1, 1) is an obstacle: 0.
-
Step 7 / 12
(1, 2): from above 1 + from the left 0.
-
Step 8 / 12
(1, 3): from above 1 + from the left 1.
-
Step 9 / 12
(2, 0): from above 1 + from the left 0.
-
Step 10 / 12
(2, 1): from above 0 + from the left 1.
-
Step 11 / 12
(2, 2): from above 1 + from the left 1.
-
Step 12 / 12
(2, 3): from above 2 + from the left 2. 4 paths.
How to think about it
The counting version of grid DP: the number of ways into an open cell is the number of ways into the cell above plus the number into the cell to the left; an obstacle has none. Row by row with one array: at an obstacle set the entry to 0, otherwise add the entry to its left to the entry (which still holds the value from above).
Complexity. Time O(rows · cols) · Space O(cols)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def unique_paths(grid):
cols = len(grid[0])
ways = [0] * cols
ways[0] = 1 if grid[0][0] == 0 else 0
for row in grid:
for c in range(cols):
if row[c] == 1:
ways[c] = 0 # an obstacle: no way in
elif c > 0:
ways[c] += ways[c - 1] # from above (already there) + from the left
return ways[cols - 1]#include <vector>
long long solve(const std::vector<std::vector<int>>& grid) {
const int cols = grid[0].size();
std::vector<long long> ways(cols, 0);
ways[0] = grid[0][0] == 0 ? 1 : 0;
for (const auto& row : grid)
for (int c = 0; c < cols; ++c) {
if (row[c] == 1) ways[c] = 0; // obstacle
else if (c > 0) ways[c] += ways[c - 1]; // above + left
}
return ways[cols - 1];
}class Solution {
public long uniquePaths(int[][] grid) {
int cols = grid[0].length;
long[] ways = new long[cols];
ways[0] = grid[0][0] == 0 ? 1 : 0;
for (int[] row : grid)
for (int c = 0; c < cols; c++) {
if (row[c] == 1) ways[c] = 0; // obstacle
else if (c > 0) ways[c] += ways[c - 1]; // above + left
}
return ways[cols - 1];
}
}function uniquePaths(grid) {
const cols = grid[0].length, ways = new Array(cols).fill(0);
ways[0] = grid[0][0] === 0 ? 1 : 0;
for (const row of grid)
for (let c = 0; c < cols; c++) {
if (row[c] === 1) ways[c] = 0; // obstacle
else if (c > 0) ways[c] += ways[c - 1]; // above + left
}
return ways[cols - 1];
}fn solve<const C: usize>(grid: &[[i64; C]]) -> i64 {
let mut ways = [0i64; C];
ways[0] = if grid[0][0] == 0 { 1 } else { 0 };
for row in grid {
for c in 0..C {
if row[c] == 1 {
ways[c] = 0; // obstacle
} else if c > 0 {
ways[c] += ways[c - 1]; // above + left
}
}
}
ways[C - 1]
}solve :: [[Int]] -> Int
solve grid = last (foldl nextRow start grid)
where
start = 1 : replicate (length (head grid) - 1) 0 -- one way to stand above the start
-- an obstacle has no ways; otherwise above (old value) plus left (new value)
nextRow above row = tail (scanl (\left (cell, up) -> if cell == 1 then 0 else left + up) 0 (zip row above))
Variants to try
- Without obstacles the answer is the binomial coefficient C(rows + cols − 2, rows − 1).
- Count modulo 10⁹ + 7 for large grids.
- Paths that must pass through a given cell: multiply the counts to it and from it.
Next problems
The cheapest path through a grid
Move only right or down from the top-left cell to the bottom-right one, paying each cell's cost. Each cell's cheapest arrival comes from above or the left.
Open the problem → Medium20 minThe largest square of ones
Find the area of the largest all-ones square in a 0/1 grid. The square ending at a cell is limited by the three squares ending next to it.
Open the problem → Hard30 minThe least starting health
Cross a grid of gains and losses, right or down, without health ever dropping to zero. The requirement at a cell depends on what comes after, so fill from the end.
Open the problem →Write a solution and run it against the real test table.
Keep going
The cheapest path through a grid
Move only right or down from the top-left cell to the bottom-right one, paying each cell's cost. Each cell's cheapest arrival comes from above or the left.
Open the problem → Medium20 minThe largest square of ones
Find the area of the largest all-ones square in a 0/1 grid. The square ending at a cell is limited by the three squares ending next to it.
Open the problem → Hard30 minThe least starting health
Cross a grid of gains and losses, right or down, without health ever dropping to zero. The requirement at a cell depends on what comes after, so fill from the end.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.