You are given an m x n
matrix maze
(0-indexed) with empty cells (represented as .
) and walls (represented as +
). You are also given the entrance
of the maze, where entrance = [entranceRow, entranceCol]
denotes the row and column of the cell you are initially standing at.
In one step, you can move one cell up, down, left, or right. You cannot step into a cell with a wall, and you cannot step outside the maze. Your goal is to find the nearest exit from the entrance
. An exit is defined as an empty cell that is at the border of the maze. The entrance does not count as an exit.
Return the number of steps in the shortest path from the entrance
to the nearest exit, or -1
if no such path exists.
Example 1:
Input: maze = [[+,+,.,+],[.,.,.,+],[+,+,+,.]], entrance = [1,2]
Output: 1
Explanation: There are 3 exits in this maze at [1,0], [0,2], and [2,3]. Initially, you are at the entrance cell [1,2].
Thus, the nearest exit is [0,2], which is 1 step away.
Example 2:
Input: maze = [[+,+,+],[.,.,.],[+,+,+]], entrance = [1,0]
Output: 2
Explanation: There is 1 exit in this maze at [1,2]. [1,0] does not count as an exit since it is the entrance cell. Initially, you are at the entrance cell [1,0].
Thus, the nearest exit is [1,2], which is 2 steps away.
Constraints:
maze.length == m
maze[i].length == n
1 <= m, n <= 100
maze[i][j]
is either .
or +
.entrance.length == 2
0 <= entranceRow < m
0 <= entranceCol < n
entrance
will always be an empty cell.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you're standing at the entrance of a maze, and you want to find the nearest exit. The brute force approach means you try every single possible path from the entrance until you find an exit, and then pick the shortest path among all the paths that lead to an exit.
Here's how the algorithm would work step-by-step:
def nearest_exit_brute_force(maze, entrance):
rows = len(maze)
cols = len(maze[0])
entrance_row = entrance[0]
entrance_col = entrance[1]
shortest_distance = float('inf')
def is_valid(row, col):
return 0 <= row < rows and 0 <= col < cols and maze[row][col] == '.'
def is_exit(row, col):
return (row == 0 or row == rows - 1 or col == 0 or col == cols - 1) and (row, col) != (entrance_row, entrance_col)
def explore_paths(row, col, distance, visited):
nonlocal shortest_distance
if is_exit(row, col):
shortest_distance = min(shortest_distance, distance)
return
# Mark current cell as visited to avoid cycles.
visited.add((row, col))
# Define possible movement directions: up, down, left, right.
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for row_direction_change, col_direction_change in directions:
new_row = row + row_direction_change
new_col = col + col_direction_change
# Only explore if new cell is valid and unvisited.
if is_valid(new_row, new_col) and (new_row, new_col) not in visited:
explore_paths(new_row, new_col, distance + 1, visited.copy())
explore_paths(entrance_row, entrance_col, 0, set())
if shortest_distance == float('inf'):
return -1
else:
return shortest_distance
The key to solving this maze problem efficiently is to explore the maze in a breadth-first manner, like ripples spreading across water. This ensures that we find the shortest path to any exit before considering longer paths. This helps avoid unnecessary exploration of longer paths.
Here's how the algorithm would work step-by-step:
def nearest_exit(maze, entrance):
rows = len(maze)
columns = len(maze[0])
queue = [(entrance[0], entrance[1], 0)]
visited = {tuple(entrance)}
# Possible directions to move (up, down, left, right).
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
while queue:
row_index, column_index, steps = queue.pop(0)
# Check if current cell is an exit
if (row_index == 0 or row_index == rows - 1 or
column_index == 0 or column_index == columns - 1) and\
(row_index, column_index) != (entrance[0], entrance[1]):
return steps
for row_direction, column_direction in directions:
new_row_index = row_index + row_direction
new_column_index = column_index + column_direction
# Only explore valid and unvisited cells.
if (0 <= new_row_index < rows and
0 <= new_column_index < columns and
maze[new_row_index][new_column_index] == '.' and
(new_row_index, new_column_index) not in visited):
# Mark as visited to avoid cycles.
visited.add((new_row_index, new_column_index))
# Add valid cells to the queue for exploration
queue.append((new_row_index, new_column_index, steps + 1))
# No exit found.
return -1
Case | How to Handle |
---|---|
Null or empty maze input | Return -1 immediately as there's no maze to traverse. |
Maze with only one cell (1x1) | If the entrance is not an exit, return -1; otherwise, return 0. |
Entrance is on the border of the maze (already an exit) | Return 0 immediately as the entrance itself is the nearest exit. |
Maze has no exits (entrance is surrounded by walls) | The BFS should terminate without finding any exits, and the algorithm should return -1. |
Entrance is blocked (surrounded by walls) | The BFS will not be able to start and will return -1 since no path is possible. |
Large maze dimensions (potential memory issues) | BFS ensures that memory usage scales linearly with the number of cells visited; avoid recursion to prevent stack overflow. |
Maze with disconnected components (entrance is in a component with no exits) | The BFS will explore the connected component and terminate without finding any exits, returning -1. |
Integer overflow potential with large maze dimensions and long path lengths | Using `int` for the distance should be sufficient, but consider using `long` if extremely large mazes are possible to avoid potential overflow. |