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.
For example:
grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
The expected output is 2.
Explanation: We have the following two paths:
Another example:
grid = [[1,0,0,0],[0,0,0,0],[0,0,0,2]]
The expected output is 4.
Explanation: We have the following four paths:
Can you provide an efficient solution to count the number of unique paths?
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:
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_count
The 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. |