Problem overview
The function computeGrid receives grid, an Array<IntArray> containing only 0 and 1, and must return a grid of the same shape in which each cell holds its distance to the nearest 0. Distance counts steps between adjacent cells, moving up, down, left or right. The grid always has at least one 0, and at most 10,000 cells.
For this input:
0 0 0
0 1 0
1 1 1
the answer is:
0 0 0
0 1 0
1 2 1
Every zero stays 0. The middle cell is one step from the zero above it. The bottom row's corners are one step from the zeros above them, and the bottom middle cell has to go two steps.
Approach: start the search from every zero at once
The obvious plan is to run a search from every 1 to find its closest zero. That repeats a lot of work. Flip the question around: instead of asking each cell where the nearest zero is, grow outward from the zeros.
Put every zero in a queue at distance 0 and run a breadth-first search (BFS). The search handles cells in rings: first the zeros, then every cell one step from a zero, then every cell two steps away, and so on. This is called a multi-source BFS. The first time the search reaches a cell, it comes from the closest zero, so that distance is final.
The grid itself can store the progress. Before the search, every 1 is overwritten with -1, meaning "not reached yet". Distances are never negative, so there is no confusion with a real answer. When the search reaches a -1 neighbour, it writes the current cell's distance plus one into it and queues it. A cell that's no longer -1 has already been handled, which is why each cell enters the queue exactly once.
Step by step
- Create the queue. An
ArrayDequeofIntArraypairs, namedque, will hold cell coordinates as[row, column]. Adding goes to the back andremoveFirstremoves from the front. - Store the dimensions.
nis the number of rows,grid.size, andmis the number of columns,grid[0].size. Both are properties, with no parentheses. - Loop over the rows with
i, and inside it loop over the columns withj, so that every cell is classified. - Queue each zero. A zero is at distance 0 from itself, so it is a starting point of the search and stays as it is in the grid.
- Mark each other cell as unreached by setting it to
-1. - Define the four directions as offsets for up, down, left and right. Each changes only one coordinate, so diagonals are excluded.
- Loop while the queue is not empty. Cells leave the queue in order of growing distance.
- Take the front cell and unpack it into
x1andy1. Taking from the back would make the search depth-first and the distances would be wrong. - Visit its four neighbours, one offset
dat a time. - Compute the neighbour's coordinates
x2andy2. They may lie outside the grid. - Test bounds and status. The neighbour must be inside the grid and still hold
-1. - Set its distance to the current cell's distance plus one, and put it in the queue so that its own neighbours are processed later.
- Return
grid, which now holds the answers.
The solution in Kotlin
class Solution {
fun computeGrid(grid: Array<IntArray>): Array<IntArray> {
val que = ArrayDeque<IntArray>()
val n = grid.size
val m = grid[0].size
for (i in 0 until n) {
for (j in 0 until m) {
if (grid[i][j] == 0) {
que.add(intArrayOf(i, j))
} else {
grid[i][j] = -1
}
}
}
val directions = arrayOf(intArrayOf(-1, 0), intArrayOf(1, 0), intArrayOf(0, -1), intArrayOf(0, 1))
while (que.isNotEmpty()) {
val front = que.removeFirst()
val x1 = front[0]
val y1 = front[1]
for (d in directions) {
val x2 = x1 + d[0]
val y2 = y1 + d[1]
if (x2 in 0 until n && y2 in 0 until m && grid[x2][y2] == -1) {
grid[x2][y2] = grid[x1][y1] + 1
que.add(intArrayOf(x2, y2))
}
}
}
return grid
}
}
The Kotlin details worth a closer look:
x2 in 0 until nis a range test: it means0 <= x2 && x2 < n, becauseuntilexcludes the end. Using0..nwould acceptn, which is one past the last row.- Short-circuit
&&.grid[x2][y2]is read only after both range tests pass, so a neighbour beyond the edge never triggers anArrayIndexOutOfBoundsException. - Pairs as
IntArray.intArrayOf(i, j)is a simple coordinate pair read back withfront[0]andfront[1]. A queue ofIntArrayis a queue of references to those small arrays. - In-place update. The function modifies the grid it receives and returns that same array, not a copy. The caller's original is overwritten.
valinside loops.front,x1,x2and the others are declared withval, so each iteration gets fresh read-only values. Only the grid's cells change.for (d in directions)loops straight over the elements, so no index is needed.
Complexity
Time: O(n * m). The setup scan visits every cell once. In the search, each cell is queued once, because it's marked the moment it's queued, and each queued cell checks four neighbours.
Space: O(n * m) in the worst case for the queue, for example when the grid is mostly zeros and all of them start in the queue. The distances reuse the input grid, so no second matrix is needed.
Edge cases and common mistakes
- All zeros. The grid comes back unchanged, because no
1is ever converted. - A single zero. For a 3 by 3 grid of ones with one zero in a corner, the farthest corner gets 4: the result is rows
4 3 2,3 2 1and2 1 0when the zero is at the bottom right. - One row or one column. The row
0 1 1 1 1gives0 1 2 3 4, and the column holding 1, 1, 0, 1 gives 2, 1, 0, 1 from top to bottom. - Starting from the ones. A search from each
1repeats work across cells. Starting from the zeros handles all the cells in one pass. - Using a stack. Taking from the back of the queue gives depth-first order, and distances are no longer shortest.
- Leaving cells at
1. If ones are not replaced by-1, the code can't tell an unvisited1from a distance of 1. - Marking too late. Setting the distance when a cell is taken from the queue, not when it is added, would let it be queued several times.
Testing
- A 3 by 3 grid with a single 1 in the centre, surrounded by zeros, comes back unchanged.
- Rows
0 0 0,0 1 0,1 1 1give rows0 0 0,0 1 0,1 2 1. - Rows
1 1 1,1 1 1,1 1 0give rows4 3 2,3 2 1,2 1 0. - The single cell
0gives0. - The row
0 1 1 1 1gives0 1 2 3 4.