You are given an n x n
grid
representing a field of cherries. Each cell is one of three possible integers: 0
(empty), 1
(cherry), or -1
(thorn, blocking the way).
Your task is to find the maximum number of cherries you can collect by following these rules:
(0, 0)
and reach the bottom-right corner (n - 1, n - 1)
by moving only right or down through valid cells (0 or 1).(n - 1, n - 1)
, return to (0, 0)
by moving only left or up through valid cells.(0, 0)
and (n - 1, n - 1)
, no cherries can be collected (return 0).Example 1:
grid = [[0, 1, -1],
[1, 0, -1],
[1, 1, 1]]
Expected Output: 5
Explanation: One possible optimal path is:
(2, 2)
, picking up 4 cherries. The grid becomes [[0, 1, -1], [0, 0, -1], [0, 0, 0]]
.(0, 0)
, picking up one more cherry.Example 2:
grid = [[1, 1, -1],
[1, -1, 1],
[-1, 1, 1]]
Expected Output: 0
Constraints:
n == grid.length
n == grid[i].length
1 <= n <= 50
grid[i][j]
is -1
, 0
, or 1
.grid[0][0] != -1
grid[n - 1][n - 1] != -1
How would you implement an algorithm to solve this problem efficiently, considering both time and space complexity? Consider the edge cases where no path exists or paths overlap.
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 two people are walking on a grid, collecting cherries. The brute force method involves trying every possible path each person can take and figuring out which combination of paths yields the most cherries.
Here's how the algorithm would work step-by-step:
def cherry_pickup_brute_force(grid):
grid_size = len(grid)
max_cherries = 0
def find_all_paths(current_row, current_col):
if current_row == grid_size - 1 and current_col == grid_size - 1:
return [[(current_row, current_col)]]
if current_row >= grid_size or current_col >= grid_size:
return []
paths = []
# Explore moving down
for path in find_all_paths(current_row + 1, current_col):
paths.append([(current_row, current_col)] + path)
# Explore moving right
for path in find_all_paths(current_row, current_col + 1):
paths.append([(current_row, current_col)] + path)
return paths
first_person_paths = find_all_paths(0, 0)
second_person_paths = find_all_paths(0, 0)
# Iterate through all combinations of paths
for first_person_path in first_person_paths:
for second_person_path in second_person_paths:
cherries_collected = 0
visited = set()
# Calculate cherries picked by the first person
for row, col in first_person_path:
if (row, col) not in visited:
cherries_collected += grid[row][col]
visited.add((row, col))
# Calculate cherries picked by the second person
for row, col in second_person_path:
if (row, col) not in visited:
cherries_collected += grid[row][col]
visited.add((row, col))
#Update maximum cherries picked
max_cherries = max(max_cherries, cherries_collected)
return max_cherries
Imagine two people walking from the top-left to the bottom-right of a grid, collecting cherries along the way. The trick is to think of these two paths as happening simultaneously to avoid re-calculating overlaps and make the problem easier to solve.
Here's how the algorithm would work step-by-step:
def cherry_pickup(grid):
grid_size = len(grid)
# Initialize DP table to store max cherries.
dp_table = {}
def get_max_cherries(row_1, column_1, row_2):
column_2 = row_1 + column_1 - row_2
# Base case: Out of bounds.
if (row_1 >= grid_size or column_1 >= grid_size or
row_2 >= grid_size or column_2 >= grid_size or
grid[row_1][column_1] == -1 or grid[row_2][column_2] == -1):
return float('-inf')
# Base case: Reached the destination.
if row_1 == grid_size - 1 and column_1 == grid_size - 1:
return grid[row_1][column_1]
# Memoization.
if (row_1, column_1, row_2) in dp_table:
return dp_table[(row_1, column_1, row_2)]
# Calculate cherries collected.
cherries = grid[row_1][column_1]
if row_1 != row_2 or column_1 != column_2:
cherries += grid[row_2][column_2]
# Recursively explore possible paths.
path_1 = get_max_cherries(row_1 + 1, column_1, row_2 + 1)
path_2 = get_max_cherries(row_1 + 1, column_1, row_2)
path_3 = get_max_cherries(row_1, column_1 + 1, row_2 + 1)
path_4 = get_max_cherries(row_1, column_1 + 1, row_2)
max_cherries = cherries + max(path_1, path_2, path_3, path_4)
dp_table[(row_1, column_1, row_2)] = max_cherries
# Store intermediate results for memoization.
return max_cherries
# Start both people at the top-left.
result = get_max_cherries(0, 0, 0)
# Handle negative results.
return max(0, result)
Case | How to Handle |
---|---|
Null or empty grid | Return 0 immediately, as there are no cherries to collect. |
1x1 grid | Return the value of the single cell if positive, otherwise 0. |
Grid with all cells containing 0 | Return 0, as no cherries can be collected. |
Grid with negative values (e.g., thorns) | Handle negative values as impassable (e.g., -1) and update the DP table accordingly by setting the cherry count to a very low number. |
Grid with only one path to the destination | The DP algorithm will still correctly compute the optimal solution along the single path. |
Maximum sized grid (50x50) with max cherry values, potential for integer overflow | Use a data type large enough to store potentially large values in the DP table, such as long. |
No path to the destination exists | Return 0, as no cherries can be collected if the destination is unreachable. |
Large grid filled with mostly zero values | The algorithm might take long time traversing zeros, but optimization using memoization (DP) should avoid recomputing. |