Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
A move slides the piece in one direction across any number of empty cells until a wall ('X') or the border stops it — every cell passed over in that slide is reachable in the same single move. Minimize moves with BFS where each pop expands all four directions over their full open run, assigning distance + 1 to newly reached cells. Stop the slide at a wall/edge, or prune it once it reaches a cell already discovered at an earlier-or-equal BFS level. O(N²) cells, near O(N²) with pruning.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you model a 'slide until blocked' move as BFS over full-row/column neighbors (constant cost per slide), rather than weighting by distance, and handle walls, borders, and revisits correctly.
Where candidates slip
Adding the slide length to the cost (turning it into a weighted problem), stopping a slide at the first already-visited cell unconditionally (which can overestimate), or using DFS and losing the shortest-path guarantee.
How to stand out
Frame it as 'redefine neighbors as the whole open run, then it's vanilla unweighted BFS', and justify the pruning rule precisely — you may only stop a slide at a cell discovered at an earlier-or-equal level, never at a same-next-level cell.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMin moves on a gridCost per move?= cells crossed= 1 per slideExplore the full treeCollapse the tree
How to read it:The middle column of 'X' forces a detour: down, right, up.
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Re-Expanding BFS (Baseline)
1Hint 1Explore hintHide hint
Each move costs 1 regardless of slide length, so this is an unweighted shortest path — what algorithm gives minimum unweighted distance?
2Hint 2Explore hintHide hint
BFS. The only change from textbook grid BFS is the neighbor rule: from a cell, slide in each of the four directions and treat every open cell on the run as a neighbor at distance + 1.
3Hint 3Explore hintHide hint
Walk each direction to the wall/border, assigning distance to unvisited cells. Continuing past visited cells re-walks lines, but it's simple and correct — O(N³).
A · Core idea
Recognizing the constant cost per slide reduces the problem to unweighted BFS; the implementation just needs the right neighbor rule.
Setup. A dist matrix initialized to -1 (unvisited); set dist[start] = 0 and enqueue the start. BFS pops cells in nondecreasing distance order.
Neighbor generation. For a popped cell (r, c), take each of the four direction vectors. Walk step by step: while the next cell is in bounds and not an X, it is reachable in one more move. If it's unvisited, set its distance to dist[r][c] + 1 and enqueue it. Then keep stepping in the same direction until a wall or the border stops the slide.
Why it's correct. Every slide is an edge of weight 1, and BFS settles all distance-d cells before any distance-(d+1) cell, so the first time a cell is assigned a distance, that distance is minimal. Returning dist[goal] (or detecting the goal on pop) yields the fewest moves.
Why we don't ship it as-is. The baseline re-walks each line every time any cell on it is processed, because it strides past already-visited cells to the wall. That redundant scanning is the O(N³) cost the optimized version trims with a pruning rule.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
grid = [".X.", ".X.", "..."], start = (0,0), goal = (0,2)Array state
init
start at (0,0)
3C · Code with teaching comments
Reference implementation previewpublic int minimumMoves(String[] grid, int startRow, int startCol, int goalRow, int goalCol) { int n = grid.length; int[][] dist = new int[n][n]; for (int[] row : dist) Arrays.fill(row, -1); int[] dr = {-1, 1, 0, 0};Explore the full codeHide the full code
D · Why it works and complexityO(N³) timeO(N²) spaceOpen the invariant and proofExplore the proofHide the proof
The whole problem is 'unweighted BFS with a custom neighbor function'. Once neighbors are defined as 'every cell in the open run', no special distance math is needed — the count of BFS layers is the answer.
ComplexityO(N³) time, O(N²) space — There are N² cells; each is dequeued once. From each, the four slides can traverse up to N cells, and the baseline keeps walking even past already-visited cells, so a single pop costs O(N). Total O(N² · N) = O(N³). With N ≤ 100 that's ~10⁶ — fast enough, but it re-scans the same lines repeatedly.
E · Edge cases and pitfalls3Adding slide length to the cost — turns an unweighted problem into a weighted one with wrong answers.Explore edge casesHide edge cases
Pitfalls
- Adding slide length to the cost — turns an unweighted problem into a weighted one with wrong answers.
- Using DFS instead of BFS — loses the shortest-path guarantee.
- Forgetting to stop the slide at an 'X' or the border.
Approach 2 of 2 · Optimal
BFS with Slide Pruning (Optimal)
1Hint 1Explore hintHide hint
The baseline re-walks each line many times. When sliding and you reach a cell already reached at distance ≤ the current cell's distance, what does that tell you about the cells beyond it?
2Hint 2Explore hintHide hint
That cell was (or will be) processed no later than now, and when processed it sweeps the same line — so the cells beyond it are already covered optimally. You can stop the slide there.
3Hint 3Explore hintHide hint
But do NOT stop at a cell whose distance equals current + 1: it was reached from a perpendicular direction and may not have swept this line yet, so keep walking past it.
A · Core idea
The correctness of BFS is untouched; the optimization removes redundant re-walking by reasoning about when a line is already fully covered.
The pruning rule. Suppose we pop cell u at distance d and slide in some direction, reaching cell v with recorded distance dv:
- If
vis unvisited (-1): assignd + 1, enqueue, and continue the slide. - If
dv ≤ d:vwas discovered at an earlier-or-equal BFS level, so it has been or will be processed by the time leveldfinishes, and its own slide in this direction sweeps every cell beyond it — assigning themdv + 1 ≤ d + 1, which is no worse than whatuwould give. So the rest of the line is already optimal; break. - If
dv = d + 1:vwas reached this level from a perpendicular slide and has not yet swept this line. Beyondvthere may be cells that onlyu's slide reaches atd + 1(viavthey'd costd + 2). So do not break — keep walking, just don't reassignv.
Why the `dv = d + 1` distinction matters. Naively breaking at the first visited cell is a classic bug: it can leave cells beyond a same-level cell to be reached one move too late, overestimating the answer. The safe rule breaks only when the encountered cell is from a strictly-or-equally-earlier layer.
Result. Every line gets fully swept exactly once — by the earliest layer that enters it — collapsing the work to O(N²). The BFS layering still guarantees the first distance assigned to each cell (and to the goal) is the minimum number of moves.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
grid = [".X.", ".X.", "..."], start = (0,0), goal = (0,2)Array state
row 0
(0,0)=0, (0,1)=X, (0,2)=3
3C · Code with teaching comments
D · Why it works and complexityO(N²) timeO(N²) spaceOpen the invariant and proofExplore the proofHide the proof
The subtlety isn't the BFS — it's knowing exactly when a slide is redundant. A cell already reached at an earlier-or-equal level dominates the rest of its line; a same-next-level cell does not. Encoding that precisely turns an O(N³) re-scan into an O(N²) sweep without sacrificing correctness.
ComplexityO(N²) time, O(N²) space — With the pruning rule, each grid line is fully swept only by the earliest BFS level that touches it; later slides into it stop immediately. Each of the N² cells is assigned once, and the total sweeping work amortizes to O(N²). Space is the O(N²) distance grid and queue.
E · Edge cases and pitfalls7start == goal — The start is popped first; the goal check returns dist = 0 with no moves.Explore edge casesHide edge cases
Edge cases handled
start == goalThe start is popped first; the goal check returns dist = 0 with no moves.goal reachable in one straight slideThe start's slide assigns the goal distance 1, returned when the goal is later popped.Walls splitting the grid into a mazeBFS layers route around 'X' cells; the answer counts each direction change as one move.
Pitfalls
- Breaking the slide at the first visited cell unconditionally — overestimates when that cell is at distance d + 1.
- Treating slide length as cost — use Dijkstra only if the problem actually weights by distance.
- Off-by-one in bounds or reading grid[nr][nc] before the bounds check.
- Returning before the goal is dequeued when distances on enqueue would also work — both are fine, but be consistent.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int minimumMoves(String[] grid, int startRow, int startCol, int goalRow, int goalCol) { int n = grid.length; int[][] dist = new int[n][n]; for (int[] row : dist) Arrays.fill(row, -1); int[] dr = {-1, 1, 0, 0};Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Weighting by slide length
Each slide is one move no matter how far it travels. Adding the number of cells crossed makes it a weighted problem and yields the wrong minimum.
Explore the code comparisonHide the code comparison
Weighting by slide length
Each slide is one move no matter how far it travels. Adding the number of cells crossed makes it a weighted problem and yields the wrong minimum.
Don't write this
dist[nr][nc] = dist[r][c] + steps # steps = cells slid — wrong cost modelWrite this instead
dist[nr][nc] = dist[r][c] + 1 # one move per slide2Unconditional break on visited
Breaking at the first already-visited cell is unsafe when that cell sits at distance d + 1 (reached perpendicularly): cells beyond it may only be reachable at d + 1 through this slide, so stopping records them as d + 2.
Explore the code comparisonHide the code comparison
Unconditional break on visited
Breaking at the first already-visited cell is unsafe when that cell sits at distance d + 1 (reached perpendicularly): cells beyond it may only be reachable at d + 1 through this slide, so stopping records them as d + 2.
Don't write this
if (dist[nr][nc] != -1) break; // breaks even on same-next-level cellsWrite this instead
if (dist[nr][nc] == -1) { dist[nr][nc] = d + 1; queue.offer(...); }
else if (dist[nr][nc] <= d) break; // only earlier/equal levels3DFS instead of BFS
Move minimization is an unweighted shortest path; DFS explores deeply and can settle a cell with a non-minimal move count, returning a wrong answer.
Explore the code comparisonHide the code comparison
DFS instead of BFS
Move minimization is an unweighted shortest path; DFS explores deeply and can settle a cell with a non-minimal move count, returning a wrong answer.
Don't write this
stack = [(start_row, start_col)] # LIFO → depth-first, not shortestWrite this instead
queue = deque([(start_row, start_col)]) # FIFO → BFS, shortest firstRevise
Carry the pattern into the interview
Pattern to remember
BFS where each move expands a full row/column run
Code pattern to memorize
dist[start] = 0, queue = [start]
while queue:
(r, c) = pop
if (r, c) == goal: return dist[r][c]
for each of 4 directions (dr, dc):
nr, nc = r+dr, c+dc
while in-bounds and grid[nr][nc] != 'X':
if dist[nr][nc] == -1:
dist[nr][nc] = dist[r][c] + 1
enqueue (nr, nc)
elif dist[nr][nc] <= dist[r][c]:
break # already swept by an earlier/equal level
nr += dr; nc += dcRules to remember
- 1One move = a full slide: every empty cell between the start and the stopping wall/edge costs the same single move.
- 2BFS (not DFS) gives the minimum because all cells at distance d are settled before any at d + 1.
- 3Keep walking past same-level cells (distance == d + 1): they were reached via a different direction and may not have swept this line yet.
- 4Prune a slide only when it hits a cell with distance ≤ current d — that cell already sweeps the rest of the line.
- 5Stop a slide at the border or an 'X'; the cell holding the 'X' is not enterable.
Use this pattern when
- Shortest path on a grid where a 'move' covers a **variable distance** in a straight line
- Unweighted in the sense that every slide costs exactly 1 → BFS
- Rook/queen/laser-style motion: slide until blocked
- You need the minimum number of moves, not the exact cells traversed
Choose another pattern when
- Each step costs the cell-to-cell distance (weighted) → Dijkstra/0-1 BFS, not plain BFS
- Moves are single-cell only → standard 4-neighbor grid BFS
- Diagonal or knight moves → different neighbor generation
- You must output the path of cells → store parent pointers, not just distances
Interview takeaway
Castle on the Grid is BFS with a non-standard neighbor rule: a node's neighbors are *all* cells reachable in one straight slide, not just the four adjacent cells. Once you redefine 'neighbor' as 'every cell in the open run', the classic unweighted-shortest-path BFS solves it unchanged.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical Unweighted BFS with a Custom Neighbor Rule. Recognize it when a single 'move' covers a variable straight-line distance: redefine a cell's neighbors as every cell in its open run, then run plain BFS. The pruning insight — stop a slide only at cells discovered at an earlier-or-equal level — drops the re-scan from O(N³) to O(N²) without affecting correctness.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Snakes and Ladders: The Quickest Way UpBFS on a board where each move jumps to up to six cells (plus snakes/ladders) — same constant-cost-per-move idea.
- Minimum Knight MovesComing soonBFS with knight-shaped neighbor generation; still one move per jump.
- Jump Game / Reachable in SlidesComing soon1-D analog: each position reaches a range in one move — BFS by layers of moves.
- Shortest Path in a Grid with ObstaclesComing soonStandard 4-neighbor grid BFS when each move is a single cell, not a slide.