1926. Nearest Exit from Entrance in Maze
Problem Description
In this problem, we are given a maze represented by a 2D matrix. Each cell of the matrix can either be an empty cell ('.'
) or a wall ('+'
). An entrance to the maze is also given to us, specified by its row and column index. We are asked to find the shortest path from the entrance to the nearest exit of the maze. Here, an exit is defined as an empty cell located on the border of the maze, excluding the entrance itself. In each step, we can move to a cell that is adjacent (left, right, up, or down) to our current position, but we cannot move into walls or outside the maze boundaries.
Our goal is to navigate from the entrance to the nearest border cell (exit) by taking the fewest steps possible. If we canโt find any path to an exit, we must return -1
. The number of steps is counted as the minimum number of moves required to reach any exit from the entrance.
Intuition
To solve this problem, we can use the Breadth-First Search (BFS) algorithm. BFS is an ideal choice for this kind of problem because it explores all possible paths level by level or, in this case, step by step from the entrance. As a result, the first time it reaches an exit, it is guaranteed to be the nearest one since BFS doesn't explore deeper paths until all paths of the current depth are explored.
We initialize BFS from the entrance by marking it as visited (to avoid revisiting) and then iteratively exploring all four adjacent cells. If an adjacent cell is empty and within the maze bounds, we check if it's an exit. If it's an exit, we immediately return the current step count since it's the minimum. If it's not, we continue the BFS by adding the cell to the queue. Importantly, as we enqueue a cell, we mark it with a wall to avoid revisiting cells that are already considered, effectively reducing unnecessary calculations.
If the BFS completes without finding an exit, we conclude that no path exists, and we return -1
, indicating failure to reach an exit.
Solution Approach
The solution provided uses the Breadth-First Search (BFS) algorithm to traverse through the maze. Let's dissect the given Python code to better understand how it translates the BFS strategy into a working solution.
1class Solution:
2 def nearestExit(self, maze: List[List[str]], entrance: List[int]) -> int:
3 m, n = len(maze), len(maze[0]) # Dimensions of the maze
4 i, j = entrance # Starting position (entrance of the maze)
5 q = deque([(i, j)]) # Initialize the queue with the entrance
6 maze[i][j] = '+' # Mark the entrance as visited by replacing it with '+'
7 ans = 0 # Steps counter
8 while q:
9 ans += 1 # Increment the step counter at the beginning of each level
10 for _ in range(len(q)): # Loop over every node in the current level
11 i, j = q.popleft() # Dequeue the front element from the queue
12 for a, b in [[0, -1], [0, 1], [-1, 0], [1, 0]]: # The 4 possible directions
13 x, y = i + a, j + b # Calculate the next cell's coordinates
14 if 0 <= x < m and 0 <= y < n and maze[x][y] == '.': # Check if it's within bounds and not visited
15 if x == 0 or x == m - 1 or y == 0 or y == n - 1: # Check if it's an exit
16 return ans # Return the current step count as the answer
17 q.append((x, y)) # Enqueue the cell for future exploration
18 maze[x][y] = '+' # Mark the cell as visited
19 return -1 # If the loop ends without finding an exit, return -1
Key Elements of the Solution:
-
Queue (
deque
): A queue is used for the BFS traversal, which follows the First-In-First-Out (FIFO) principle. This ensures that cells are explored in the order they are reached. -
Visited Marking: When a cell is visited, it is marked by changing its value to
'+'
. This prevents the algorithm from re-visiting the same cell, which would otherwise lead to infinite loops and incorrect step count. -
Direction Array: A simple 2D array
[[0, -1], [0, 1], [-1, 0], [1, 0]]
is used to represent the four possible moves from any cell (left, right, up, down). -
Checking Exit Condition: An exit is an empty cell (
'.'
) at the border of the maze. Whenever moving to a new cell, the algorithm checks if it is an exit by comparing its coordinates with the boundary of the maze. -
Steps Counter: The variable
ans
is used to count the number of steps taken to reach an exit. It gets incremented at the start of processing each level, ensuring the correct step count when an exit is found.
Efficiency of the Solution:
The solution is efficient for finding the shortest path in a maze scenario. BFS ensures that the first time we find an exit, it must be the closest one because we explore all possible paths of equal length before moving on to longer paths. The time and space complexity of this solution are both O(m*n), where m
is the number of rows and n
is the number of columns in the maze.
Example Walkthrough
Let's walk through a small example to illustrate the solution approach. Consider the following 5x5 maze as an example, where E
is the entrance, .
represents an empty cell, and +
represents a wall:
1+ + + + + 2+ . . . + 3+ . E . + 4+ . . . + 5+ + + . +
Following the BFS strategy to navigate the maze from the entrance to the closest exit:
- Initialize the queue with the entrance coordinates, which is
(2, 2)
here, and set the starting cell as visited by marking it with '+':
1+ + + + + 2+ . . . + 3+ . + . + 4+ . . . + 5+ + + . +
- Process the first node in the queue, we explore its adjacent cells:
(2, 1)
,(2, 3)
,(1, 2)
, and(3, 2)
. These cells are not yet visited; we add them to the queue and mark them as visited:
1+ + + + + 2+ . + . + 3+ + + . + 4+ . + . + 5+ + + . +
-
We increment our steps counter
ans = 1
as we have started moving from the entrance. Now we process the cells in the queue. For each cell, we check its adjacent cells. For example, start with cell(2, 1)
and check(1, 1)
,(3, 1)
,(2, 0)
, and(2, 2)
. Here,(2, 0)
is not within the bounds,(2, 2)
is already visited, and(1, 1)
and(3, 1)
are available for further exploration. -
As we continue to explore the current level, we find that the cell
(1, 2)
is just one step away from an exit as it is located on the border of the maze. We've reached an exit:
1+ + + + + 2+ . + . + 3+ + + . + 4+ . + . + 5+ + + . +
- Since we've encountered an exit, we immediately return the current step count, which is
ans = 1
.
By the BFS approach, we've managed to find the nearest exit to the entrance in the fewest steps as possible without unnecessary calculations. If no exit had been reached, we would have continued to process the BFS queue until it was empty and then returned -1
to indicate no available exit.
Python Solution
1from collections import deque
2
3class Solution:
4 def nearest_exit(self, maze, entrance):
5 # Get the dimensions of the maze
6 rows, cols = len(maze), len(maze[0])
7
8 # Entrance coordinates
9 row_entrance, col_entrance = entrance
10
11 # Initialize a queue with the starting position (entrance)
12 queue = deque([(row_entrance, col_entrance)])
13
14 # Mark the starting position as visited by changing it to '+'
15 maze[row_entrance][col_entrance] = '+'
16
17 # Initialize the number of steps taken to exit
18 steps = 0
19
20 # Breadth-first search (BFS) loop
21 while queue:
22 # Increment the steps at the start of each level of BFS
23 steps += 1
24
25 # Go through each position at the current level
26 for _ in range(len(queue)):
27 # Get position from queue
28 row, col = queue.popleft()
29
30 # Directions in which we can move: left, right, up, down
31 directions = [[0, -1], [0, 1], [-1, 0], [1, 0]]
32
33 # Check all four directions
34 for direction_row, direction_col in directions:
35 # Calculate new position
36 new_row, new_col = row + direction_row, col + direction_col
37
38 # Check if the new position is within the maze boundaries
39 # and if it has not been visited (maze cell is '.')
40 if 0 <= new_row < rows and 0 <= new_col < cols and maze[new_row][new_col] == '.':
41 # Check if the new position is on the edge of the maze, which means an exit is found
42 if new_row == 0 or new_row == rows - 1 or new_col == 0 or new_col == cols - 1:
43 return steps
44
45 # Otherwise, add the position to the queue and mark as visited
46 queue.append((new_row, new_col))
47 maze[new_row][new_col] = '+'
48
49 # If we have not found an exit, return -1 indicating failure to find an exit
50 return -1
51
Java Solution
1class Solution {
2 public int nearestExit(char[][] maze, int[] entrance) {
3 // Maze dimensions
4 int rowCount = maze.length;
5 int colCount = maze[0].length;
6
7 // Queue for BFS
8 Queue<int[]> queue = new LinkedList<>();
9 queue.offer(entrance);
10
11 // Mark the entrance as visited
12 maze[entrance[0]][entrance[1]] = '+';
13
14 // Step count for nearest exit
15 int steps = 0;
16
17 // Directions for exploring neighbors (up, right, down, left)
18 int[] directions = {-1, 0, 1, 0, -1};
19
20 // Begin BFS
21 while (!queue.isEmpty()) {
22 steps++; // Increment steps at each level
23
24 for (int count = queue.size(); count > 0; count--) {
25 int[] currentPos = queue.poll(); // Poll the current position from the queue
26
27 // Iterate through all possible directions
28 for (int l = 0; l < 4; l++) {
29 int nextRow = currentPos[0] + directions[l];
30 int nextCol = currentPos[1] + directions[l + 1];
31
32 // Check if the next position is within bounds and not a wall
33 if (nextRow >= 0 && nextRow < rowCount && nextCol >= 0 && nextCol < colCount && maze[nextRow][nextCol] == '.') {
34 // Check if the next position is at the border, thus an exit
35 if (nextRow == 0 || nextRow == rowCount - 1 || nextCol == 0 || nextCol == colCount - 1) {
36 return steps; // Return the number of steps to reach this exit
37 }
38 // Mark the position as visited
39 queue.offer(new int[] {nextRow, nextCol});
40 maze[nextRow][nextCol] = '+';
41 }
42 }
43 }
44 }
45 // If no exit was found, return -1
46 return -1;
47 }
48}
49
C++ Solution
1#include <vector>
2#include <queue>
3using namespace std;
4
5class Solution {
6public:
7 int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
8 // Initialize the maze dimensions
9 int rows = maze.size(), cols = maze[0].size();
10
11 // Queue to store the path in (x, y) coordinates
12 queue<vector<int>> queue{{entrance}};
13
14 // Mark the entrance as visited
15 maze[entrance[0]][entrance[1]] = '+';
16
17 // Initialize the step counter
18 int steps = 0;
19
20 // Possible directions to move: up, right, down, left
21 vector<int> directions = {-1, 0, 1, 0, -1};
22
23 // Perform breadth-first search to find the nearest exit
24 while (!queue.empty()) {
25 // Increment the step counter at the start of each level of BFS
26 ++steps;
27
28 // Process all nodes on the current level
29 for (int count = queue.size(); count > 0; --count) {
30 // Get the current position
31 auto position = queue.front();
32 queue.pop();
33
34 // Explore all possible directions from the current position
35 for (int i = 0; i < 4; ++i) {
36 int x = position[0] + directions[i], y = position[1] + directions[i + 1];
37
38 // Check if the new position is within bounds and not visited
39 if (x >= 0 && x < rows && y >= 0 && y < cols && maze[x][y] == '.') {
40 // Check if the new position is an exit
41 if (x == 0 || x == rows - 1 || y == 0 || y == cols - 1) return steps;
42
43 // Add the new position to the queue and mark as visited
44 queue.push({x, y});
45 maze[x][y] = '+';
46 }
47 }
48 }
49 }
50
51 // Return -1 if no exit is found
52 return -1;
53 }
54};
55
Typescript Solution
1interface Coordinates {
2 x: number;
3 y: number;
4}
5
6function nearestExit(maze: string[][], entrance: [number, number]): number {
7 // Initialize the maze dimensions
8 const rows = maze.length;
9 const cols = maze[0].length;
10
11 // Initialize a queue with the starting point containing x and y coordinates based on the entrance
12 const queue: Coordinates[] = [{ x: entrance[0], y: entrance[1] }];
13
14 // Mark the entrance as visited
15 maze[entrance[0]][entrance[1]] = '+';
16
17 // Initialize the step counter
18 let steps = 0;
19
20 // Possible directions to move: up, right, down, left (represented by x, y deltas)
21 const directions = [{ x: -1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 0 }, { x: 0, y: -1 }];
22
23 // Perform breadth-first search to find the nearest exit
24 while (queue.length > 0) {
25 // Increment the step counter at the start of each level of BFS
26 steps++;
27
28 // Process all nodes on the current level
29 for (let count = queue.length; count > 0; count--) {
30 // Get the current position by dequeueing from the front of the array
31 const position = queue.shift();
32
33 // Explore all possible directions from the current position
34 for (const direction of directions) {
35 const newX = position.x + direction.x;
36 const newY = position.y + direction.y;
37
38 // Check if the new position is within bounds and not visited
39 if (newX >= 0 && newX < rows && newY >= 0 && newY < cols && maze[newX][newY] === '.') {
40 // Check if the new position is an exit
41 if (newX === 0 || newX === rows - 1 || newY === 0 || newY === cols - 1) {
42 return steps;
43 }
44
45 // Add the new position to the queue and mark as visited
46 queue.push({ x: newX, y: newY });
47 maze[newX][newY] = '+';
48 }
49 }
50 }
51 }
52
53 // Return -1 if no exit is found
54 return -1;
55}
56
Time and Space Complexity
The time complexity of the given code is O(M*N)
, where M
is the number of rows and N
is the number of columns in the maze. This complexity arises because, in the worst-case scenario, the algorithm needs to traverse all the cells in the maze before finding an exit or determining that no exit is reachable. Traversal is done via breadth-first search (BFS), and each cell is visited at most once, as visited cells are marked with '+' to prevent revisiting.
The space complexity is also O(M*N)
due to the same reasoning. The space is used to store the queue q
, which, in the worst case, could contain all the cells as we explore the maze. Furthermore, we modify the maze to mark visited positions, which also takes O(M*N)
space; however, this does not add to the complexity as it is the same maze that is passed as input and not additional space being used.
Got a question?ย Ask the Teaching Assistantย anything you don't understand.
Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.