Master the Rat in a Maze problem — finding all paths from source to destination in a binary grid, backtracking with visited tracking, directional movement (4-directional and 8-directional), finding shortest path, path reconstruction, and variations including multiple exit points.
Problem Statement
A maze is represented as an N×N binary grid where:
- 1 = open cell (can pass through)
- 0 = blocked cell (wall)
A rat starts at the top-left corner (0, 0) and must reach the bottom-right corner (N-1, N-1). Find all possible paths from source to destination.
| Path 1: DDRDRR — (0,0) | (1,0)→(2,0)... wait, (2,0)=0, invalid |
| Let's re-read: (0,0) | (1,0)→(1,1)→(2,1)→(3,1)→(3,2)→(3,3) ✓ |
| Path: D | R → D → D → R → R |
Solution with Coordinate Path
def find_all_paths_with_coords(maze):
"""Returns paths as sequences of (row, col) coordinates."""
n = len(maze)
result = []
visited = [[False] * n for _ in range(n)]
def backtrack(row, col, path):
if row == n-1 and col == n-1:
result.append(list(path))
return
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = row+dr, col+dc
if 0 <= nr < n and 0 <= nc < n and maze[nr][nc] == 1 and not visited[nr][nc]:
visited[nr][nc] = True
path.append((nr, nc))
backtrack(nr, nc, path)
path.pop()
visited[nr][nc] = False
if maze[0][0] == 1:
visited[0][0] = True
backtrack(0, 0, [(0,0)])
return result
Visualizing a Path
def visualize_path(maze, path_coords):
"""Print the maze with the path marked."""
n = len(maze)
path_set = set(path_coords)
for r in range(n):
row_str = ""
for c in range(n):
if (r, c) in path_set:
row_str += "* " # Path marker
elif maze[r][c] == 1:
row_str += "1 "
else:
row_str += "0 "
print(row_str)
print()
maze = [[1,0,0,0],[1,1,0,1],[0,1,0,0],[1,1,1,1]]
paths = find_all_paths_with_coords(maze)
print(f"Found {len(paths)} path(s)")
if paths:
print("First path:")
visualize_path(maze, paths[0])
Shortest Path in Maze — BFS Approach
Backtracking finds ALL paths but not necessarily the SHORTEST. For shortest path, use BFS:
from collections import deque
def shortest_path_maze(maze):
"""
Find shortest path using BFS (not backtracking).
BFS guarantees shortest path in unweighted grid.
Time: O(n²), Space: O(n²)
"""
n = len(maze)
if not maze[0][0] or not maze[n-1][n-1]:
return []
queue = deque([(0, 0, [(0,0)])]) # (row, col, path_so_far)
visited = [[False]*n for _ in range(n)]
visited[0][0] = True
while queue:
row, col, path = queue.popleft()
if row == n-1 and col == n-1:
return path # First complete path = shortest
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = row+dr, col+dc
if 0 <= nr < n and 0 <= nc < n and maze[nr][nc] == 1 and not visited[nr][nc]:
visited[nr][nc] = True
queue.append((nr, nc, path + [(nr, nc)]))
return [] # No path exists
maze = [[1,0,0,0],[1,1,0,1],[0,1,0,0],[1,1,1,1]]
short = shortest_path_maze(maze)
print(f"Shortest path length: {len(short)}")
print(f"Path: {short}")
8-Directional Movement
def find_paths_8dir(maze):
"""
Rat can move in all 8 directions (including diagonals).
"""
n = len(maze)
result = []
visited = [[False]*n for _ in range(n)]
# 8 directions: U,D,L,R + 4 diagonals
directions = [
(-1,0,'U'), (1,0,'D'), (0,-1,'L'), (0,1,'R'),
(-1,-1,'UL'), (-1,1,'UR'), (1,-1,'DL'), (1,1,'DR')
]
def backtrack(r, c, path):
if r == n-1 and c == n-1:
result.append(list(path))
return
for dr, dc, name in directions:
nr, nc = r+dr, c+dc
if 0 <= nr < n and 0 <= nc < n and maze[nr][nc] == 1 and not visited[nr][nc]:
visited[nr][nc] = True
path.append(name)
backtrack(nr, nc, path)
path.pop()
visited[nr][nc] = False
if maze[0][0]:
visited[0][0] = True
backtrack(0, 0, [])
return result
Count Paths Only (No Storage)
def count_paths(maze):
"""
Count paths without storing them — saves memory.
"""
n = len(maze)
visited = [[False]*n for _ in range(n)]
def backtrack(r, c):
if r == n-1 and c == n-1:
return 1 # Found one path
count = 0
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = r+dr, c+dc
if 0 <= nr < n and 0 <= nc < n and maze[nr][nc] == 1 and not visited[nr][nc]:
visited[nr][nc] = True
count += backtrack(nr, nc)
visited[nr][nc] = False
return count
if maze[0][0]:
visited[0][0] = True
return backtrack(0, 0)
return 0
print(count_paths([[1,1,1],[1,0,1],[1,1,1]])) # 2 paths
Complexity Analysis
| Approach | Time | Space | Use When |
|---|
| Backtracking (all paths) | O(4^(n²)) worst | O(n²) | Need all paths |
| BFS (shortest path) | O(n²) | O(n²) | Need shortest path |
| Count only | O(4^(n²)) | O(n²) | Need count, not paths |
| DP (path count) | O(n²) | O(n²) | Count paths in simple grid |
Note: The DP approach works only when there are no cycles (only Down+Right movement). For full 4-directional movement, backtracking or BFS is required.
Common Interview Variations
- Print all paths: Standard backtracking (shown above)
- Count paths: Remove result storage, return counts
- Shortest path: Use BFS instead of DFS/backtracking
- Path with minimum cost: BFS with cost weights (Dijkstra's)
- Multiple starting/ending points: Run BFS from all sources simultaneously
Summary
The Rat in a Maze problem demonstrates core backtracking concepts:
- State: Current position (row, col) + visited cells
- Choice: Which direction to move (D/U/L/R)
- Constraint: Cell must be open (1) and unvisited
- Undo: Unmark the visited cell when backtracking
The visited array is critical — without it, the algorithm would revisit cells infinitely. The backtracking ensures the visited array is always restored to its correct state before trying a different direction.
*Next Lesson: Sudoku Solver*