You are given an m x n integer array grid where grid[i][j] could be:
1 representing the starting square. There is exactly one starting square.2 representing the ending square. There is exactly one ending square.0 representing empty squares we can walk over.-1 representing obstacles that we cannot walk over.Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
Example 1:
Input: grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]] Output: 2 Explanation: We have the following two paths: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2) 2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)
Example 2:
Input: grid = [[1,0,0,0],[0,0,0,0],[0,0,0,2]] Output: 4 Explanation: We have the following four paths: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3) 2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3) 3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3) 4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)
Example 3:
Input: grid = [[0,1],[2,0]] Output: 0 Explanation: There is no path that walks over every empty square exactly once. Note that the starting and ending square can be anywhere in the grid.
Constraints:
m == grid.lengthn == grid[i].length1 <= m, n <= 201 <= m * n <= 20-1 <= grid[i][j] <= 2When 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:
The brute force approach is like exploring every possible route in a maze. We'll try going in every direction from the starting point, one step at a time, until we either find the exit or hit a dead end.
Here's how the algorithm would work step-by-step:
def unique_paths_iii_brute_force(grid):
number_of_rows = len(grid)
number_of_columns = len(grid[0])
start_row, start_column = -1, -1
empty_cells_count = 0
for row in range(number_of_rows):
for column in range(number_of_columns):
if grid[row][column] == 1:
start_row, start_column = row, column
if grid[row][column] == 0:
empty_cells_count += 1
path_count = 0
def explore_paths(row, column, visited_cells):
nonlocal path_count
# Check boundaries and obstacles.
if row < 0 or row >= number_of_rows or column < 0 or column >= number_of_columns or grid[row][column] == -1:
return
# If we reached the end.
if grid[row][column] == 2:
# Ensure all empty cells are visited.
if visited_cells == empty_cells_count + 1:
path_count += 1
return
# Mark current cell as visited
grid[row][column] = -1
# Recursively explore all possible paths
explore_paths(row + 1, column, visited_cells + 1)
explore_paths(row - 1, column, visited_cells + 1)
explore_paths(row, column + 1, visited_cells + 1)
explore_paths(row, column - 1, visited_cells + 1)
# Backtrack: Restore cell value for other paths.
grid[row][column] = 0 if grid[row][column] != 1 else 1
explore_paths(start_row, start_column, 0)
return path_countThe core idea is to explore every possible path while ensuring we visit every empty square exactly once. To avoid re-exploring the same squares, we use a technique that marks squares as visited during our exploration and unmarks them when we backtrack.
Here's how the algorithm would work step-by-step:
def uniquePathsIII(grid): rows = len(grid)
columns = len(grid[0])
start_row, start_column = 0, 0
empty_squares = 1
for row in range(rows):
for column in range(columns):
if grid[row][column] == 1:
start_row, start_column = row, column
elif grid[row][column] == 0:
empty_squares += 1
path_count = 0
def depthFirstSearch(row, column, remaining_empty_squares):
nonlocal path_count
# Reached the destination and visited all empty cells
if grid[row][column] == 2 and remaining_empty_squares == 0:
path_count += 1
return
# Possible movements
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for delta_row, delta_column in directions:
new_row = row + delta_row
new_column = column + delta_column
# Bounds and obstacle check
if 0 <= new_row < rows and 0 <= new_column < columns and \
grid[new_row][new_column] >= 0:
# Mark as visited.
grid[new_row][new_column] = -2
depthFirstSearch(new_row, new_column, remaining_empty_squares - 1)
# Backtrack, unmark the square
grid[new_row][new_column] = 0
# Initiate the search
grid[start_row][start_column] = -2
# Start dfs from starting point
depthFirstSearch(start_row, start_column, empty_squares)
return path_count| Case | How to Handle |
|---|---|
| Null or empty grid | Return 0 if the grid is null or has zero rows or columns as there are no paths. |
| Grid with no starting or ending point | Return 0 if the grid doesn't contain exactly one start (1) and one end (2). |
| Grid with no empty cells (all obstacles) | Return 0 if the number of empty cells plus start and end is not equal to the number of cells visited, since no path exists to visit all cells. |
| Grid where start and end are adjacent, and all other cells are obstacles | Return 1 if the adjacent cells are obstacles and a path is available, representing the path from start to end. |
| Maximum grid size (e.g., 20x20) leading to deep recursion | Ensure the solution uses a backtracking approach with pruning or optimization to avoid stack overflow errors by caching the visited cells. |
| Grid with only one possible path | Verify that the solution correctly finds the single unique path when it exists, ensuring the logic handles cases with only one valid route. |
| Integer overflow when calculating the number of empty cells if grid is very large. | Use a data type with a larger range, like long, to store the count of empty cells if the grid size is significantly large. |
| Multiple starting or ending points in the grid | Return 0 if more than one starting (1) or ending point (2) exists in the grid, since the problem specifies exactly one of each. |