Neighborhood reasoning
Neighbourhood reasoning is how most grid algorithms move: 4- and 8-direction neighbours, direction vectors, inside-grid checks, obstacles and multi-source starts.
Learning goals
- Generate neighbours with direction vectors
- Write one reusable bounds and obstacle check
- Start a search from many cells at once
Prerequisites
4-direction
In 4-direction movement a cell (r, c) has up to four neighbours: up, right, down and left, at offsets (-1, 0), (0, 1), (1, 0) and (0, -1). This is the usual meaning of adjacent in grid path problems, and the neighbours are at Manhattan distance 1.
Pick a cell and read which of its four neighbours are valid.
1static final int[] DR = {-1, 0, 1, 0}, DC = {0, 1, 0, -1}; // up, right, down, left2for (int d = 0; d < 4; d++) {3int nr = r + DR[d], nc = c + DC[d];4// process (nr, nc) after the bounds check5}
Neighbours, direction vectors and bounds checks
Interactive · edit the inputgrid · 3 × 4
valid neighbours
Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here.
1static final int[] DR4 = {-1, 0, 1, 0}, DC4 = {0, 1, 0, -1}; // up, right, down, left2static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // adds the diagonals3static List<int[]> neighbors(int[][] g, int r, int c, boolean eight) {4int[] dr = eight ? DR8 : DR4, dc = eight ? DC8 : DC4;5List<int[]> out = new ArrayList<>();6for (int d = 0; d < dr.length; d++) {7int nr = r + dr[d], nc = c + dc[d];8if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside the grid9if (g[nr][nc] == 0) continue; // wall10out.add(new int[]{nr, nc});11}12return out;13}
Live state
r
1
c
1
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | r=1 c=1 | Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here. |
| 2 | L10 | d=0 (dr, dc)=(-1, 0) (nr, nc)=(0, 1) | up: (0, 1) is open: a valid neighbour. |
| 3 | L9 | d=1 (dr, dc)=(0, 1) (nr, nc)=(1, 2) | right: (1, 2) is a wall. |
| 4 | L10 | d=2 (dr, dc)=(1, 0) (nr, nc)=(2, 1) | down: (2, 1) is open: a valid neighbour. |
| 5 | L10 | d=3 (dr, dc)=(0, -1) (nr, nc)=(1, 0) | left: (1, 0) is open: a valid neighbour. |
| 6 | L12 | return=3 | 3 valid neighbours out of 4 directions. Corners and edges have fewer neighbours, which is why the bounds check is needed. |
8-direction
8-direction movement adds the four diagonals, so a cell has up to eight neighbours, at Chebyshev distance 1 (like a chess king). Use it for “touching including corners” rules such as Minesweeper counts or the game of life.
Switch the direction option to 8 and compare the neighbour count at a corner.
1static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // clockwise from up2int count = 0;3for (int d = 0; d < 8; d++) {4int nr = r + DR8[d], nc = c + DC8[d];5if (nr >= 0 && nr < R && nc >= 0 && nc < C && g[nr][nc] == 1) count++;6}
Neighbours, direction vectors and bounds checks
Interactive · edit the inputgrid · 3 × 4
valid neighbours
Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here.
1static final int[] DR4 = {-1, 0, 1, 0}, DC4 = {0, 1, 0, -1}; // up, right, down, left2static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // adds the diagonals3static List<int[]> neighbors(int[][] g, int r, int c, boolean eight) {4int[] dr = eight ? DR8 : DR4, dc = eight ? DC8 : DC4;5List<int[]> out = new ArrayList<>();6for (int d = 0; d < dr.length; d++) {7int nr = r + dr[d], nc = c + dc[d];8if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside the grid9if (g[nr][nc] == 0) continue; // wall10out.add(new int[]{nr, nc});11}12return out;13}
Live state
r
1
c
1
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | r=1 c=1 | Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here. |
| 2 | L10 | d=0 (dr, dc)=(-1, 0) (nr, nc)=(0, 1) | up: (0, 1) is open: a valid neighbour. |
| 3 | L9 | d=1 (dr, dc)=(0, 1) (nr, nc)=(1, 2) | right: (1, 2) is a wall. |
| 4 | L10 | d=2 (dr, dc)=(1, 0) (nr, nc)=(2, 1) | down: (2, 1) is open: a valid neighbour. |
| 5 | L10 | d=3 (dr, dc)=(0, -1) (nr, nc)=(1, 0) | left: (1, 0) is open: a valid neighbour. |
| 6 | L12 | return=3 | 3 valid neighbours out of 4 directions. Corners and edges have fewer neighbours, which is why the bounds check is needed. |
Direction vectors
A direction vector (dr, dc) is the change in row and column for one step. Storing the vectors in two small arrays (or one int[][]) replaces four or eight copies of nearly identical code with one loop. A knight's moves, hex grids and longer jumps are just other sets of vectors.
Keep the order consistent (for example clockwise) so direction indexes can be reused, such as (d + 1) % 4 for a right turn.
| Direction | dr | dc |
|---|---|---|
| Up | -1 | 0 |
| Right | 0 | 1 |
| Down | 1 | 0 |
| Left | 0 | -1 |
Neighbours, direction vectors and bounds checks
Interactive · edit the inputgrid · 3 × 4
valid neighbours
Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here.
1static final int[] DR4 = {-1, 0, 1, 0}, DC4 = {0, 1, 0, -1}; // up, right, down, left2static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // adds the diagonals3static List<int[]> neighbors(int[][] g, int r, int c, boolean eight) {4int[] dr = eight ? DR8 : DR4, dc = eight ? DC8 : DC4;5List<int[]> out = new ArrayList<>();6for (int d = 0; d < dr.length; d++) {7int nr = r + dr[d], nc = c + dc[d];8if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside the grid9if (g[nr][nc] == 0) continue; // wall10out.add(new int[]{nr, nc});11}12return out;13}
Live state
r
1
c
1
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | r=1 c=1 | Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here. |
| 2 | L10 | d=0 (dr, dc)=(-1, 0) (nr, nc)=(0, 1) | up: (0, 1) is open: a valid neighbour. |
| 3 | L9 | d=1 (dr, dc)=(0, 1) (nr, nc)=(1, 2) | right: (1, 2) is a wall. |
| 4 | L10 | d=2 (dr, dc)=(1, 0) (nr, nc)=(2, 1) | down: (2, 1) is open: a valid neighbour. |
| 5 | L10 | d=3 (dr, dc)=(0, -1) (nr, nc)=(1, 0) | left: (1, 0) is open: a valid neighbour. |
| 6 | L12 | return=3 | 3 valid neighbours out of 4 directions. Corners and edges have fewer neighbours, which is why the bounds check is needed. |
Inside-grid checks
Before touching g[nr][nc] confirm that 0 <= nr < R and 0 <= nc < C. Do it first, with short-circuit &&, so an invalid index is never evaluated. Wrapping the check in a helper inBounds(r, c) keeps search code readable; a border of sentinel cells (padding the grid with walls) is an alternative that removes the check entirely.
Pick a corner cell: some directions land outside and are skipped.
1static boolean inBounds(int r, int c, int R, int C) { return r >= 0 && r < R && c >= 0 && c < C; }2if (inBounds(nr, nc, R, C) && g[nr][nc] != WALL) {3// safe to use (nr, nc)4}
Neighbours, direction vectors and bounds checks
Interactive · edit the inputgrid · 3 × 4
valid neighbours
Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here.
1static final int[] DR4 = {-1, 0, 1, 0}, DC4 = {0, 1, 0, -1}; // up, right, down, left2static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // adds the diagonals3static List<int[]> neighbors(int[][] g, int r, int c, boolean eight) {4int[] dr = eight ? DR8 : DR4, dc = eight ? DC8 : DC4;5List<int[]> out = new ArrayList<>();6for (int d = 0; d < dr.length; d++) {7int nr = r + dr[d], nc = c + dc[d];8if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside the grid9if (g[nr][nc] == 0) continue; // wall10out.add(new int[]{nr, nc});11}12return out;13}
Live state
r
1
c
1
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | r=1 c=1 | Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here. |
| 2 | L10 | d=0 (dr, dc)=(-1, 0) (nr, nc)=(0, 1) | up: (0, 1) is open: a valid neighbour. |
| 3 | L9 | d=1 (dr, dc)=(0, 1) (nr, nc)=(1, 2) | right: (1, 2) is a wall. |
| 4 | L10 | d=2 (dr, dc)=(1, 0) (nr, nc)=(2, 1) | down: (2, 1) is open: a valid neighbour. |
| 5 | L10 | d=3 (dr, dc)=(0, -1) (nr, nc)=(1, 0) | left: (1, 0) is open: a valid neighbour. |
| 6 | L12 | return=3 | 3 valid neighbours out of 4 directions. Corners and edges have fewer neighbours, which is why the bounds check is needed. |
Multi-source starts
A multi-source search starts from several cells at once: put all of them in the queue with distance 0 before the search begins. The wave then spreads from every source simultaneously, so each cell receives the distance to its nearest source. This solves “distance to the nearest 0” and “how long until everything rots”.
Switch the BFS animation to multi-source mode.
1Deque<int[]> queue = new ArrayDeque<>();2int[][] dist = new int[R][C];3for (int[] row : dist) Arrays.fill(row, -1);4for (int r = 0; r < R; r++)5for (int c = 0; c < C; c++)6if (g[r][c] == 2) { dist[r][c] = 0; queue.offer(new int[]{r, c}); } // every source starts at distance 0
BFS on a grid: shortest path and multi-source distances
Interactive · edit the inputgrid · 3 × 5
queue (front first)
Start at the top-left cell with distance 0.
1static int shortestPath(int[][] g) { // 4-directional moves, 0 = wall2int R = g.length, C = g[0].length;3int[][] dist = new int[R][C]; for (int[] row : dist) Arrays.fill(row, -1);4Deque<int[]> queue = new ArrayDeque<>();5// single source: (0, 0); multi-source: every cell equal to 2, all at distance 06dist[0][0] = 0; queue.offer(new int[]{0, 0});7while (!queue.isEmpty()) {8int[] cur = queue.poll();9for (int[] d : new int[][]{{-1, 0}, {0, 1}, {1, 0}, {0, -1}}) {10int r = cur[0] + d[0], c = cur[1] + d[1];11if (r < 0 || r >= R || c < 0 || c >= C || g[r][c] == 0 || dist[r][c] != -1) continue;12dist[r][c] = dist[cur[0]][cur[1]] + 1; // first arrival is the shortest, because BFS goes level by level13queue.offer(new int[]{r, c});14}15}16return dist[R - 1][C - 1]; // -1 when unreachable; multi-source: max over all cells17}
Live state
sources
1
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L5 | sources=1 | Start at the top-left cell with distance 0. |
| 2 | L12 | cell=(0,0) dist=0 | Expand (0,0) at distance 0: reached (0,1)=1. |
| 3 | L12 | cell=(0,1) dist=1 | Expand (0,1) at distance 1: reached (1,1)=2. |
| 4 | L12 | cell=(1,1) dist=2 | Expand (1,1) at distance 2: reached (1,2)=3, (2,1)=3. |
| 5 | L12 | cell=(1,2) dist=3 | Expand (1,2) at distance 3: reached (1,3)=4, (2,2)=4. |
| 6 | L12 | cell=(2,1) dist=3 | Expand (2,1): every open neighbour is already reached or blocked. |
| 7 | L12 | cell=(1,3) dist=4 | Expand (1,3) at distance 4: reached (0,3)=5, (1,4)=5, (2,3)=5. |
| 8 | L12 | cell=(2,2) dist=4 | Expand (2,2): every open neighbour is already reached or blocked. |
| 9 | L12 | cell=(0,3) dist=5 | Expand (0,3) at distance 5: reached (0,4)=6. |
| 10 | L12 | cell=(1,4) dist=5 | Expand (1,4) at distance 5: reached (2,4)=6. |
| 11 | L12 | cell=(2,3) dist=5 | Expand (2,3): every open neighbour is already reached or blocked. |
| 12 | L12 | cell=(0,4) dist=6 | Expand (0,4): every open neighbour is already reached or blocked. |
| 13 | L12 | cell=(2,4) dist=6 | Expand (2,4): every open neighbour is already reached or blocked. |
| 14 | L16 | return=6 | The bottom-right cell is 6 steps away, the minimum, because BFS reaches cells in order of distance. |
Obstacles
Obstacles (walls, water, blocked cells) are cells you may not enter. Treat them exactly like out-of-bounds: reject the neighbour in the same check. Decide a clear convention for the grid (for example 0 = wall, 1 = open) and apply it in the single neighbour helper.
The grid in the animation marks walls as blocked neighbours.
1static List<int[]> openNeighbors(int[][] g, int r, int c) {2List<int[]> out = new ArrayList<>();3for (int d = 0; d < 4; d++) {4int nr = r + DR[d], nc = c + DC[d];5if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside6if (g[nr][nc] == 0) continue; // wall7out.add(new int[]{nr, nc});8}9return out;10}
Neighbours, direction vectors and bounds checks
Interactive · edit the inputgrid · 3 × 4
valid neighbours
Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here.
1static final int[] DR4 = {-1, 0, 1, 0}, DC4 = {0, 1, 0, -1}; // up, right, down, left2static final int[] DR8 = {-1, -1, 0, 1, 1, 1, 0, -1}, DC8 = {0, 1, 1, 1, 0, -1, -1, -1}; // adds the diagonals3static List<int[]> neighbors(int[][] g, int r, int c, boolean eight) {4int[] dr = eight ? DR8 : DR4, dc = eight ? DC8 : DC4;5List<int[]> out = new ArrayList<>();6for (int d = 0; d < dr.length; d++) {7int nr = r + dr[d], nc = c + dc[d];8if (nr < 0 || nr >= g.length || nc < 0 || nc >= g[0].length) continue; // outside the grid9if (g[nr][nc] == 0) continue; // wall10out.add(new int[]{nr, nc});11}12return out;13}
Live state
r
1
c
1
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | r=1 c=1 | Cell 5 is row 1, column 1. A direction vector (dr, dc) added to (r, c) gives each neighbour; 4 directions here. |
| 2 | L10 | d=0 (dr, dc)=(-1, 0) (nr, nc)=(0, 1) | up: (0, 1) is open: a valid neighbour. |
| 3 | L9 | d=1 (dr, dc)=(0, 1) (nr, nc)=(1, 2) | right: (1, 2) is a wall. |
| 4 | L10 | d=2 (dr, dc)=(1, 0) (nr, nc)=(2, 1) | down: (2, 1) is open: a valid neighbour. |
| 5 | L10 | d=3 (dr, dc)=(0, -1) (nr, nc)=(1, 0) | left: (1, 0) is open: a valid neighbour. |
| 6 | L12 | return=3 | 3 valid neighbours out of 4 directions. Corners and edges have fewer neighbours, which is why the bounds check is needed. |
Practice ladder
Now try it yourself
Easy
- List the 4-direction neighbours of (1, 1) in a 3 × 3 grid
- How many neighbours does a corner cell have with 8 directions?
Medium
- Count the mines around each cell of a Minesweeper board
- Why is the bounds check written before the g[nr][nc] test?
Advanced
- Use a padded grid with a wall border to remove all bounds checks from a BFS
Key takeaways
- Direction arrays turn neighbour code into one loop.
- Check bounds first, then obstacles; wrap both in one helper.
- Seed all sources at distance 0 for a multi-source search.