Coding Interview Patterns Module 20 – Graph patterns: traversal
BFS with extra state: keys and wall breaks
Find shortest paths when you may break up to k walls or must collect keys: put the extra state in every BFS node, count the states first, then prune.
What you will learn
- Add the remaining wall breaks or the keys held to the state of a BFS
- Estimate the number of states before writing the search
- Track visited states instead of visited cells
- Remove dominated states with one best value per cell
- Encode collected keys as a bitmask and test doors with bit operations
Before you start
What you’ll learn · Before you start
What you will learn
- Add the remaining wall breaks or the keys held to the state of a BFS
- Estimate the number of states before writing the search
- Track visited states instead of visited cells
- Remove dominated states with one best value per cell
- Encode collected keys as a bitmask and test doors with bit operations
Before you start
On this page
The shortest path in a grid when you may break through up to k obstacles is a BFS whose nodes carry more than the cell. Two visits to the same cell differ in how many breaks are still allowed, so the node becomes (row, column, breaks left), and the first time the search reaches the target in any such state is the fewest steps. The same idea covers doors that need keys, fuel that runs out and anything else that changes which moves are allowed. The work is in choosing that extra state and counting the states before you code.
The problem: a robot that may drill through rock
The problem
A collapsed mine is a grid of rows strings: '.' is open and '#' is rock. A rescue robot starts at the
top-left cell and must reach the bottom-right cell, moving one cell up, down, left or right per step. It may drill
through at most k rock cells; entering a rock cell takes one step like any other. Return the fewest steps, or −1.
Limits: 1 ≤ rows, cols ≤ 40 and 0 ≤ k ≤ rows × cols; the two corner cells are open.
- On the 6 × 7 mine in the diagram, the robot needs 17 steps with k = 0, 13 with k = 1 and 11 with k = 2.
["..##", "##..", "...."]with k = 0 gives −1: the start is walled in. With k = 1 it gives 5.- A 40 × 40 block of rock, open only at the two corners, gives 78 steps once k is at least 77.
The mine, and the fewest steps when one rock cell may be drilled
Text description of the diagram
The mine has 6 rows, numbered 0 to 5 from the top, and 7 columns, numbered 0 to 6 from the left. Rock cells are grey; open cells are white. The robot starts at row 0, column 0 and must reach row 5, column 6.
The route for k = 1 is shaded and each of its cells carries its step number:
- Steps 0 to 2 go down column 0 to row 2, steps 3 and 4 go right along row 2 to column 2.
- Steps 5 to 7 go up to row 1 and right along it to column 4.
- Step 8 drills through the rock at row 2, column 4, outlined with a dashed line.
- Steps 9 to 12 go to row 2, column 5 and down column 5 to row 5; step 13 is the exit at row 5, column 6.
Without a drill the shortest route takes 17 steps, round the top right; with two drills it takes 11, the fewest any route can take on this map.
The brute force: try every set of drilled cells
Choose which rock cells to drill, open them, and run a plain BFS; try every set of at most k cells and keep the best answer. It is correct, and on the mine it is quick, because the mine has only 16 rock cells:
"""Fewest steps when up to k rock cells may be drilled, by brute force: try every set of at most k rock cells, drill
them, and run a plain BFS. Correct, and the number of sets explodes with k."""
from collections import deque
from itertools import combinations
from math import comb
MINE = [".#.#...",
".#...#.",
"...##..",
"...##.#",
".###..#",
".##.#.."]
def plain_bfs(grid):
"""Fewest steps from the top-left to the bottom-right cell over open cells, or -1."""
rows, cols = len(grid), len(grid[0])
dist = [[-1] * cols for _ in range(rows)]
dist[0][0] = 0
queue = deque([(0, 0)])
while queue:
r, c = queue.popleft()
for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == "." and dist[nr][nc] == -1:
dist[nr][nc] = dist[r][c] + 1
queue.append((nr, nc))
return dist[rows - 1][cols - 1]
def by_brute_force(grid, k):
rocks = [(r, c) for r, row in enumerate(grid) for c, x in enumerate(row) if x == "#"]
best, searches = -1, 0
for size in range(k + 1):
for drilled in combinations(rocks, size):
opened = [list(row) for row in grid]
for r, c in drilled:
opened[r][c] = "."
searches += 1
d = plain_bfs(["".join(row) for row in opened])
if d != -1 and (best == -1 or d < best):
best = d
return best, searches
rocks = sum(row.count("#") for row in MINE)
print(f"The mine: {len(MINE)} x {len(MINE[0])} cells, {rocks} of them rock")
for k in range(4):
best, searches = by_brute_force(MINE, k)
print(f" k = {k}: {best} steps, after {searches:,} {'search' if searches == 1 else 'searches'}")
print(f"A 40 x 40 mine with 480 rock cells and k = 5: {sum(comb(480, s) for s in range(6)):,} searches") Output
The mine: 6 x 7 cells, 16 of them rock k = 0: 17 steps, after 1 search k = 1: 13 steps, after 17 searches k = 2: 11 steps, after 137 searches k = 3: 11 steps, after 697 searches A 40 x 40 mine with 480 rock cells and k = 5: 210,147,849,977 searches
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 drill_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The searches grow like the number of sets: 1, 17, 137 and 697 for k up to 3. A 40 × 40 mine with 480 rock cells and k = 5 needs 210,147,849,977 of them, each a search of up to 1,600 cells, and k can be in the hundreds.
What to notice
The cell alone does not decide what the robot can do next: the drills it has left matter too. A robot at a cell with two drills left can follow routes that the same robot with none cannot. Make the drills left part of the node, and the problem becomes a plain BFS over at most rows × cols × (k + 1) states, each reached at most once. Keys work alike: a robot that holds the key to a door has moves that one without it lacks, so the set of keys held joins the state.
The state: row, column and drills left Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- How many states, before any code
- Why the visited check is per state: the bug reproduced
- The code in five languages
- Keeping only the best state per cell, measured
- Keys and doors: a bitmask in the state
- Variants
- Edge cases and common bugs
- Exercise: collect every key
- Fewest steps to collect every key
- A practice set with answers
- Check yourself
- Interview questions
- In a grid BFS that may break through up to k walls, why is k never usefully larger than rows + cols − 3, and what does that cap save?
- A teammate's solution to the shortest path with up to k wall breaks marks cells as visited. It passes the samples and fails hidden tests. Give a small failing input, explain why it fails, fix it, and say what the fix costs in memory on a 40 × 40 grid with k = 40.
- A warehouse robot on a 50 × 50 floor must collect up to 12 keys, and doors open only with their keys. Estimate the states of a BFS over (cell, keys held), decide whether it fits in time and memory, and say what you would change if there were 20 keys.
- Breaking a wall now costs 5 units of time while an open step costs 1, and breaks are unlimited. Why does BFS no longer give the fastest route, what replaces it, and what changes when breaks are also limited to k?
- Key takeaways
5 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 21 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- breadth-first search (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- bit vector (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- Bitwise operations on integer types (The Python Standard Library) (Python Software Foundation)
- math.comb (The Python Standard Library) (Python Software Foundation)
- Uint8Array (JavaScript reference) (MDN Web Docs)
- The Java Language Specification, Java SE 25: 15.22 Bitwise and logical operators (Oracle)
- The Go Programming Language Specification: Min and max (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress