1926. Nearest Exit from Entrance in Maze
Problem Description
You are given a maze represented as an m x n
matrix where:
- Empty cells are marked with
'.'
- Walls are marked with
'+'
You start at a given entrance position entrance = [entrance_row, entrance_col]
inside the maze.
Your task is to find the shortest path from the entrance to the nearest exit. An exit is defined as any empty cell ('.'
) that is located at the border of the maze (first/last row or first/last column). Note that the entrance itself doesn't count as an exit.
You can move one step at a time in four directions: up, down, left, or right. However:
- You cannot move into walls (
'+'
) - You cannot move outside the maze boundaries
Return the minimum number of steps needed to reach the nearest exit from the entrance. If no path to an exit exists, return -1
.
For example, if you start at position [1, 2]
in a maze and the nearest exit is 3 steps away at the border, you would return 3
. If you're surrounded by walls and cannot reach any border, you would return -1
.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The maze can be represented as a graph where each empty cell is a node, and edges connect adjacent empty cells that we can move between.
Is it a tree?
- No: The maze is not a tree structure. It's a grid graph where cells can have multiple paths between them and may contain cycles.
Is the problem related to directed acyclic graphs?
- No: The maze is an undirected graph (we can move in all four directions), and it may contain cycles.
Is the problem related to shortest paths?
- Yes: We need to find the shortest path from the entrance to the nearest exit.
Is the graph Weighted?
- No: Each move from one cell to another has the same cost (1 step). All edges have equal weight.
BFS
- Yes: Since we have an unweighted graph and need to find the shortest path, BFS is the appropriate algorithm.
Conclusion: The flowchart suggests using BFS (Breadth-First Search) for finding the shortest path from the entrance to the nearest exit in an unweighted maze. BFS guarantees finding the shortest path in unweighted graphs because it explores nodes level by level, ensuring that when we first reach an exit, it's via the shortest possible path.
Intuition
When we need to find the shortest path in a maze, we should think about how we would explore it systematically. If we were physically in the maze, we'd want to explore all positions that are 1 step away from our starting point first, then all positions that are 2 steps away, then 3 steps away, and so on. This level-by-level exploration ensures that the first time we reach an exit, we've found it via the shortest possible route.
This is exactly what BFS does - it explores nodes in order of their distance from the starting point. We start at the entrance and expand outward like ripples in water, checking all cells at distance 1, then distance 2, and so forth.
The key insight is that since each move costs the same (1 step), we don't need complex algorithms like Dijkstra's. BFS naturally handles this unweighted shortest path problem. When we first encounter a cell that's on the border of the maze (and is empty), we know we've found the nearest exit because BFS guarantees we've explored all closer cells first.
To implement this, we use a queue to maintain the order of exploration. We mark visited cells (by changing '.'
to '+'
in the solution) to avoid revisiting them and getting stuck in cycles. The algorithm terminates either when we find an exit (return the number of steps) or when we've explored all reachable cells without finding an exit (return -1
).
The entrance itself isn't considered an exit even if it's on the border, so we mark it as visited immediately and start our search from there. Each "layer" of BFS represents one additional step from the entrance, which is why we process the queue level by level and increment our step counter after each complete level.
Learn more about Breadth-First Search patterns.
Solution Approach
Following the BFS approach identified earlier, let's walk through the implementation step by step:
1. Initialize the BFS Setup
- Extract the dimensions of the maze:
m
(rows) andn
(columns) - Get the starting position from
entrance
:i, j = entrance
- Create a queue
q
usingdeque
and add the entrance position(i, j)
to it - Mark the entrance as visited by setting
maze[i][j] = "+"
to prevent revisiting - Initialize
ans = 0
to track the number of steps
2. Level-by-Level BFS Traversal The key to finding the shortest path is processing the queue level by level:
- Before processing each level, increment
ans += 1
(representing one more step from the entrance) - Process all cells currently in the queue (this represents all cells at the same distance)
- For each cell
(i, j)
in the current level:- Check all four directions:
[[0, -1], [0, 1], [-1, 0], [1, 0]]
(left, right, up, down) - Calculate the new position:
x, y = i + a, j + b
- Check all four directions:
3. Validate and Process New Positions
For each new position (x, y)
:
- Check if it's within bounds:
0 <= x < m and 0 <= y < n
- Check if it's an empty cell:
maze[x][y] == "."
- If both conditions are met:
- Check if it's an exit: If
x == 0 or x == m - 1 or y == 0 or y == n - 1
, this cell is on the border, so returnans
immediately - If not an exit, add
(x, y)
to the queue for the next level - Mark as visited:
maze[x][y] = "+"
to avoid revisiting
- Check if it's an exit: If
4. Handle No Solution Case
If the queue becomes empty (all reachable cells have been explored) and no exit was found, return -1
.
The algorithm efficiently explores the maze level by level, guaranteeing that when an exit is found, it's reached via the shortest path. The time complexity is O(m Γ n)
as we visit each cell at most once, and the space complexity is O(min(m, n))
for the queue in the worst case.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the BFS solution approach.
Consider this 4Γ4 maze where we start at position entrance = [1, 2]
:
maze = [ ['+', '+', '.', '+'], ['.', '.', '.', '+'], ['+', '.', '.', '.'], ['+', '+', '+', '.'] ]
Initial Setup:
- Start at position (1, 2)
- Mark entrance as visited:
maze[1][2] = '+'
- Queue:
[(1, 2)]
- Steps:
ans = 0
Level 1 (ans = 1): Process position (1, 2) and explore its 4 neighbors:
- Up (0, 2): It's a
'.'
and on the border (row 0) β Found exit! Return 1
Let's consider a different example where the exit isn't immediately adjacent:
maze = [ ['+', '+', '+', '+'], ['.', '.', '.', '+'], ['+', '.', '+', '.'], ['+', '+', '+', '.'] ] entrance = [1, 0]
Initial Setup:
- Start at (1, 0) - this is on the border but entrance doesn't count as exit
- Mark entrance:
maze[1][0] = '+'
- Queue:
[(1, 0)]
- Steps:
ans = 0
Level 1 (ans = 1): Process (1, 0):
- Right (1, 1): Valid
'.'
β Add to queue, mark as'+'
- Other directions: walls or out of bounds
- Queue after level:
[(1, 1)]
Level 2 (ans = 2): Process (1, 1):
- Right (1, 2): Valid
'.'
β Add to queue, mark as'+'
- Down (2, 1): Valid
'.'
β Add to queue, mark as'+'
- Queue after level:
[(1, 2), (2, 1)]
Level 3 (ans = 3): Process (1, 2):
- All neighbors are walls or visited Process (2, 1):
- Right (2, 3): Valid
'.'
and on border (column 3) β Found exit! Return 3
The BFS guarantees we find the shortest path because we explore all cells at distance 1, then distance 2, then distance 3, finding the exit at the minimum distance of 3 steps.
Solution Implementation
1class Solution:
2 def nearestExit(self, maze: List[List[str]], entrance: List[int]) -> int:
3 # Get maze dimensions
4 rows, cols = len(maze), len(maze[0])
5
6 # Initialize BFS queue with entrance position
7 start_row, start_col = entrance
8 queue = deque([(start_row, start_col)])
9
10 # Mark entrance as visited by changing it to wall
11 maze[start_row][start_col] = "+"
12
13 # Track the number of steps taken
14 steps = 0
15
16 # BFS to find nearest exit
17 while queue:
18 steps += 1
19
20 # Process all cells at current distance level
21 for _ in range(len(queue)):
22 current_row, current_col = queue.popleft()
23
24 # Check all four directions: up, down, left, right
25 directions = [[0, -1], [0, 1], [-1, 0], [1, 0]]
26 for row_offset, col_offset in directions:
27 next_row = current_row + row_offset
28 next_col = current_col + col_offset
29
30 # Check if next position is valid and is an empty cell
31 if 0 <= next_row < rows and 0 <= next_col < cols and maze[next_row][next_col] == ".":
32 # Check if this empty cell is an exit (on the border)
33 if next_row == 0 or next_row == rows - 1 or next_col == 0 or next_col == cols - 1:
34 return steps
35
36 # Add valid cell to queue for next level exploration
37 queue.append((next_row, next_col))
38
39 # Mark as visited by changing to wall
40 maze[next_row][next_col] = "+"
41
42 # No exit found
43 return -1
44
1class Solution {
2 public int nearestExit(char[][] maze, int[] entrance) {
3 // Get maze dimensions
4 int rows = maze.length;
5 int cols = maze[0].length;
6
7 // Direction vectors for moving up, right, down, left
8 // Using pairs: (-1,0), (0,1), (1,0), (0,-1)
9 final int[] directions = {-1, 0, 1, 0, -1};
10
11 // Initialize BFS queue with the entrance position
12 Deque<int[]> queue = new ArrayDeque<>();
13 queue.offer(entrance);
14
15 // Mark entrance as visited by changing it to '+'
16 maze[entrance[0]][entrance[1]] = '+';
17
18 // BFS to find shortest path to exit
19 // Start with distance 1 (entrance doesn't count as a step)
20 for (int distance = 1; !queue.isEmpty(); distance++) {
21 // Process all cells at current distance level
22 int currentLevelSize = queue.size();
23
24 for (int i = 0; i < currentLevelSize; i++) {
25 // Get current position from queue
26 int[] currentPosition = queue.poll();
27
28 // Try all 4 directions
29 for (int dir = 0; dir < 4; dir++) {
30 // Calculate new position
31 int newRow = currentPosition[0] + directions[dir];
32 int newCol = currentPosition[1] + directions[dir + 1];
33
34 // Check if new position is valid and is an empty cell
35 if (newRow >= 0 && newRow < rows &&
36 newCol >= 0 && newCol < cols &&
37 maze[newRow][newCol] == '.') {
38
39 // Check if this position is an exit (on the border)
40 if (newRow == 0 || newRow == rows - 1 ||
41 newCol == 0 || newCol == cols - 1) {
42 return distance;
43 }
44
45 // Mark as visited and add to queue for further exploration
46 maze[newRow][newCol] = '+';
47 queue.offer(new int[] {newRow, newCol});
48 }
49 }
50 }
51 }
52
53 // No exit found
54 return -1;
55 }
56}
57
1class Solution {
2public:
3 int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
4 int rows = maze.size();
5 int cols = maze[0].size();
6
7 // Direction vectors for moving up, right, down, left
8 // Using the pattern: dirs[i] and dirs[i+1] form (dx, dy) pairs
9 int directions[5] = {-1, 0, 1, 0, -1};
10
11 // BFS queue to store positions to explore
12 queue<pair<int, int>> bfsQueue;
13
14 // Start BFS from entrance position
15 bfsQueue.emplace(entrance[0], entrance[1]);
16
17 // Mark entrance as visited by changing it to wall
18 maze[entrance[0]][entrance[1]] = '+';
19
20 // BFS level by level, tracking distance (number of steps)
21 for (int distance = 1; !bfsQueue.empty(); ++distance) {
22 // Process all cells at current distance level
23 int currentLevelSize = bfsQueue.size();
24
25 for (int i = 0; i < currentLevelSize; ++i) {
26 // Get current position from queue
27 auto [currentRow, currentCol] = bfsQueue.front();
28 bfsQueue.pop();
29
30 // Explore all 4 adjacent directions
31 for (int dir = 0; dir < 4; ++dir) {
32 int nextRow = currentRow + directions[dir];
33 int nextCol = currentCol + directions[dir + 1];
34
35 // Check if next position is valid and is an empty cell
36 if (nextRow >= 0 && nextRow < rows &&
37 nextCol >= 0 && nextCol < cols &&
38 maze[nextRow][nextCol] == '.') {
39
40 // Check if this position is an exit (on the border)
41 if (nextRow == 0 || nextRow == rows - 1 ||
42 nextCol == 0 || nextCol == cols - 1) {
43 return distance;
44 }
45
46 // Mark as visited and add to queue for further exploration
47 maze[nextRow][nextCol] = '+';
48 bfsQueue.emplace(nextRow, nextCol);
49 }
50 }
51 }
52 }
53
54 // No exit found
55 return -1;
56 }
57};
58
1/**
2 * Finds the shortest path from entrance to the nearest exit in a maze using BFS
3 * @param maze - 2D array where '.' is empty cell and '+' is wall
4 * @param entrance - Starting position [row, col]
5 * @returns Minimum steps to reach nearest exit, or -1 if no exit exists
6 */
7function nearestExit(maze: string[][], entrance: number[]): number {
8 // Direction vectors for moving up, right, down, left
9 const directions: number[] = [0, 1, 0, -1, 0];
10
11 // BFS queue: each element is [row, col, steps]
12 const queue: number[][] = [[...entrance, 0]];
13
14 // Mark entrance as visited by changing it to wall
15 maze[entrance[0]][entrance[1]] = '+';
16
17 // Process queue using BFS
18 for (const [currentRow, currentCol, steps] of queue) {
19 // Try all 4 directions
20 for (let dirIndex = 0; dirIndex < 4; dirIndex++) {
21 const nextRow: number = currentRow + directions[dirIndex];
22 const nextCol: number = currentCol + directions[dirIndex + 1];
23
24 // Get the cell value, returns undefined if out of bounds
25 const cellValue: string | undefined = maze[nextRow]?.[nextCol];
26
27 // Check if we reached an exit (out of bounds and not from entrance)
28 if (!cellValue && steps > 0) {
29 return steps;
30 }
31
32 // If cell is empty, add to queue and mark as visited
33 if (cellValue === '.') {
34 queue.push([nextRow, nextCol, steps + 1]);
35 maze[nextRow][nextCol] = '+';
36 }
37 }
38 }
39
40 // No exit found
41 return -1;
42}
43
Time and Space Complexity
Time Complexity: O(m Γ n)
In the worst case, the BFS algorithm needs to visit every cell in the maze exactly once. Each cell is added to the queue at most once (when it's first discovered and marked as visited by changing it to "+"
), and each cell is processed at most once when it's dequeued. For each cell, we check at most 4 neighboring cells, which is a constant operation. Therefore, the total time complexity is O(m Γ n)
, where m
is the number of rows and n
is the number of columns in the maze.
Space Complexity: O(m Γ n)
The space complexity comes from two main sources:
- The BFS queue
q
which in the worst case can containO(m Γ n)
cells (though typically it would be much less, containing only cells at the current BFS frontier) - The modification of the input maze array to mark visited cells with
"+"
, which effectively uses the input array as a visited tracking mechanism
Even though we modify the input array in-place rather than creating a separate visited array, the queue can still grow to contain a significant portion of the cells in the maze, particularly in cases where the maze has large open areas. In the worst case scenario, the queue could contain up to O(m Γ n)
elements, making the overall space complexity O(m Γ n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrectly Counting the Entrance as an Exit
The Problem: A common mistake is checking if the current position is an exit before exploring its neighbors. Since we start at the entrance position, if the entrance happens to be on the border of the maze, the algorithm might incorrectly return 0 or 1 steps.
Why It Happens: The code processes the entrance position first in the queue. If we check for exit conditions at the beginning of processing each cell rather than when discovering new cells, we might mistakenly identify the entrance as an exit.
Incorrect Implementation:
while queue:
for _ in range(len(queue)):
current_row, current_col = queue.popleft()
# WRONG: Checking if current position is an exit
if current_row == 0 or current_row == rows - 1 or current_col == 0 or current_col == cols - 1:
return steps # This would return 0 if entrance is on border!
# ... rest of the code
The Solution: Only check if a cell is an exit when we discover it as a neighbor, not when we process it from the queue. This ensures we never consider the entrance as an exit.
Pitfall 2: Incrementing Steps at the Wrong Time
The Problem: Placing steps += 1
inside the inner loop or after processing each cell instead of before processing each level leads to incorrect step counting.
Why It Happens: BFS processes cells level by level, where each level represents cells at the same distance from the start. The step counter should increment once per level, not once per cell.
Incorrect Implementation:
while queue:
for _ in range(len(queue)):
current_row, current_col = queue.popleft()
steps += 1 # WRONG: This increments for each cell, not each level
# ... rest of the code
The Solution: Increment steps
once at the beginning of each while loop iteration, before processing all cells at the current level. This ensures all cells at the same distance share the same step count.
Pitfall 3: Not Capturing Queue Size Before Processing
The Problem: Using range(len(queue))
dynamically or not storing the queue size can lead to processing cells from different levels together.
Why It Happens: As we add new cells to the queue while processing the current level, the queue size changes. If we don't capture the initial size, we might process newly added cells in the same iteration.
Incorrect Implementation:
while queue: steps += 1 # WRONG: Not processing level by level current_row, current_col = queue.popleft() # This processes one cell at a time, mixing levels
The Solution: Use for _ in range(len(queue))
where len(queue)
is evaluated once at the beginning of each level. This ensures we only process cells that were in the queue at the start of this level, maintaining proper distance tracking.
Which of the following is a good use case for backtracking?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!