Problem overview
The function uniquePaths(grid) receives a grid as an Array<IntArray> with n rows and m columns. A 0 is an empty cell and a 1 is an obstacle. A robot starts at the top-left cell and wants to reach the bottom-right cell, moving only down or right. The function returns how many different paths avoid every obstacle. The statement promises that the answer fits in a 32-bit integer and that n and m are at most 100.
In the example, the 3x3 grid has a single obstacle in the middle, at row 1, column 1. Two paths exist: right, right, down, down along the top and right edges, and down, down, right, right along the left and bottom edges. The answer is 2.
Approach: count the ways into each cell
Every path to a cell arrives from the cell above or from the cell to the left. So the number of paths to a cell is the sum of the number of paths to those two neighbours. Let dp[i][j] be the number of paths from (0, 0) to (i, j). Then dp[i][j] = dp[i - 1][j] + dp[i][j - 1] for a free cell, and 0 for an obstacle, since no path may stand on it.
The first row and the first column have only one neighbour each, so they are filled separately. A cell in the first column is reachable only by going straight down, so it has one path, unless an obstacle sits between it and the start. Once an obstacle blocks the column, every cell below it has no path. The first row works the same way going right.
Trying every path with recursion would explore a number of paths that grows exponentially. The table touches each cell once.
Step by step
- Read the dimensions.
nisgrid.size, the number of rows, andmisgrid[0].size, the number of columns. - Create
dp, annbymtable of zeros. - Fill the first column. Loop
ifrom 0 untiln. Ifgrid[i][0]is an obstacle,break: nothing below it can be reached, and those cells keep their 0. - Mark the cell reachable. Otherwise set
dp[i][0] = 1, one straight path down from the start. Thebreakalready skips this line for a blocked cell, so noelseis needed. - Fill the first row the same way with
jfrom 0 untilm. Both loops setdp[0][0]when the start is free, which is harmless. - Fill the rest. Loop
ifrom 1 untilnandjfrom 1 untilm. The neighbours above and to the left are already computed when a cell is reached. - Skip obstacles. Only when
grid[i][j] == 0, setdp[i][j]to the sum of the cell above and the cell to the left. An obstacle cell stays 0. - Return the bottom-right cell,
dp[n - 1][m - 1]. If that cell is itself an obstacle, it is still 0, which is the right answer.
The solution in Kotlin
class Solution {
fun uniquePaths(grid: Array<IntArray>): Int {
val n = grid.size
val m = grid[0].size
val dp = Array(n) { IntArray(m) }
for (i in 0 until n) {
if (grid[i][0] == 1) {
break
}
dp[i][0] = 1
}
for (j in 0 until m) {
if (grid[0][j] == 1) {
break
}
dp[0][j] = 1
}
for (i in 1 until n) {
for (j in 1 until m) {
if (grid[i][j] == 0) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
}
}
}
return dp[n - 1][m - 1]
}
}
The Kotlin details worth a closer look:
Array<IntArray>. The grid is an array whose elements areIntArrays, sogrid[i][j]reads rowi, columnj.grid.sizecounts rows andgrid[0].sizecounts columns. This shape is whatArray(n) { IntArray(m) }builds, with the lambda creating a separate row each time.breakinside afor. It leaves only the innermost loop it sits in. In the first two loops that is the whole loop, which is what we want. Writing an extraelsewould be redundant, because the code after theifonly runs when theifdidn't fire.untilversus...0 until nstops atn - 1, the last valid index.0..nwould includenand throwArrayIndexOutOfBoundsException.valtable, mutable cells.val dpfixes which arraydpnames, but its cells can still be assigned.
Complexity
Time: O(n * m). Every cell is filled once, and each fill is a constant-time addition.
Space: O(n * m). The dp table has the same size as the grid. A row-by-row version could keep just one row, but the full table is easier to read.
Edge cases and common mistakes
- Obstacle at the start.
[[1]]returns 0: the first loop breaks at once, and the bottom-right cell is the start cell. - Single free cell.
[[0]]returns 1, because the robot is already at the finish. - Obstacle at the finish.
[[0, 0], [0, 1]]returns 0. - A single row or column.
[[0, 0, 0, 0]]returns 1, and[[0, 1, 0, 0]]returns 0, since the obstacle cuts the only route. The nested loops never run when there is only one row or column. - An open grid. A 3x3 grid of zeros returns 6.
- Forgetting the
break. Without it, cells below a blocked first-column cell would be marked reachable and the counts would be too high. Acontinuewould also be wrong, for the same reason. - Overwriting obstacles. If the recurrence runs for a cell with a 1, the table would count paths through the obstacle. The check on
grid[i][j] == 0prevents that.
Testing
- The 3x3 grid
[[0, 0, 0], [0, 1, 0], [0, 0, 0]]gives2. [[0, 0, 0], [0, 0, 0], [0, 0, 0]]gives6.[[0, 1], [0, 0]]gives1.[[0, 0], [0, 1]]gives0.[[1]]gives0, and[[0]]gives1.
