You are starving and want to eat food as quickly as possible. You want to find the shortest path to arrive at any food cell.
You are given an m x n
character matrix, grid
, of these different types of cells:
'*'
is your location. There is exactly one '*'
cell.'#'
is a food cell. There may be multiple food cells.'O'
is free space, and you can travel through these cells.'X'
is an obstacle, and you cannot travel through these cells.You can travel to any adjacent cell north, east, south, or west in one step.
Return the length of the shortest path to arrive at any food cell. If there is no food cell, return -1
.
Example 1:
Input: grid = [["X","X","X","X","X","X","X","X"],["X","*","O","O","O","O","O","X"],["X","O","O","#","O","O","O","X"],["X","O","O","O","O","O","O","X"],["X","O","O","O","O","O","O","X"],["X","O","O","O","O","O","O","X"],["X","O","O","O","O","O","O","X"],["X","X","X","X","X","X","X","X"]]
Output: 3 Explanation: It takes 3 steps to reach the food.
Example 2:
Input: grid = [["X","X","X","X","X"],["X","*","X","O","X"],["X","O","X","#","X"],["X","X","X","X","X"]]
Output: -1 Explanation: There is no path to reach the food.
Example 3:
Input: grid = [["X","X","X","X","X","X","X","X","X","X"],["X","*","O","O","O","O","O","O","#","X"],["X","X","X","X","X","X","X","X","X","X"]]
Output: 7 Explanation: It takes 7 steps to reach the food.
Constraints:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
grid[row][col]
is '*'
, 'X'
, 'O'
, or '#'
.grid
has exactly one '*'
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 exploring a maze where you need to find the shortest path to food. The brute force approach is like trying every single possible route, no matter how long or winding, until you find food. It’s like leaving breadcrumbs everywhere you go to remember where you've already been and avoid going in circles.
Here's how the algorithm would work step-by-step:
def shortest_path_to_food_brute_force(grid):
start_row, start_col = -1, -1
rows, cols = len(grid), len(grid[0])
for row in range(rows):
for col in range(cols):
if grid[row][col] == '*':
start_row, start_col = row, col
break
if start_row != -1:
break
shortest_path = float('inf')
def explore(row, col, steps, visited):
nonlocal shortest_path
# Check for out-of-bounds or visited cells
if row < 0 or row >= rows or col < 0 or col >= cols or (row, col) in visited or grid[row][col] == 'X':
return
# If we found food, update the shortest path
if grid[row][col] == '#':
shortest_path = min(shortest_path, steps)
return
visited.add((row, col))
# Explore all 4 directions
explore(row + 1, col, steps + 1, visited.copy())
explore(row - 1, col, steps + 1, visited.copy())
explore(row, col + 1, steps + 1, visited.copy())
explore(row, col - 1, steps + 1, visited.copy())
explore(start_row, start_col, 0, set())
if shortest_path == float('inf'):
return -1
else:
return shortest_path
The quickest way to find food is to explore the map layer by layer, always moving to the nearest possible locations. This ensures that we find the food using the shortest path possible because we expand our search outwards in a systematic way.
Here's how the algorithm would work step-by-step:
def shortest_path_to_get_food(grid):
queue = []
visited = set()
start_row, start_col = -1, -1
rows = len(grid)
cols = len(grid[0])
for row_index in range(rows):
for col_index in range(cols):
if grid[row_index][col_index] == '*':
start_row, start_col = row_index, col_index
break
if start_row != -1:
break
queue.append((start_row, start_col, 0))
visited.add((start_row, start_col))
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
while queue:
current_row, current_col, path_length = queue.pop(0)
# We found food, so return current path length
if grid[current_row][current_col] == '#':
return path_length
for row_direction, col_direction in directions:
new_row = current_row + row_direction
new_col = current_col + col_direction
# Ensure new coordinates are within grid bounds
if 0 <= new_row < rows and 0 <= new_col < cols and \
grid[new_row][new_col] != 'X' and (new_row, new_col) not in visited:
# Mark as visited to prevent re-visits
visited.add((new_row, new_col))
queue.append((new_row, new_col, path_length + 1))
return -1
Case | How to Handle |
---|---|
Null or empty grid | Return -1 immediately indicating no path exists as the grid is invalid. |
Grid with only one cell, not food or start | Return -1 immediately as there's no food or starting point. |
Grid with only starting point but no food | Return -1 after BFS completes without finding 'food'. |
Grid with only food but no starting point | Return -1 immediately as no path can exist from the non-existent start point. |
Starting point is surrounded by walls (no path possible) | BFS will enqueue the starting point, but no valid neighbor can be enqueued, eventually returning -1 when the queue is empty. |
Food is unreachable due to walls or other obstacles | BFS will explore all reachable cells and return -1 if it never encounters the food cell. |
Large grid size, potentially causing memory issues | BFS's queue size can grow linearly with the number of cells in the grid; therefore, if memory becomes a concern, consider using iterative deepening A* or other memory-efficient search algorithms. |
Multiple food locations exist (find the shortest path to any food) | The BFS will find the shortest path to the *first* food encountered during the search, effectively solving the problem if multiple food sources were required. |