Before attempting this problem, you should be comfortable with:
Instead of thinking about one person going from the top-left to the bottom-right and then back, we can simulate two people starting from the top-left and moving simultaneously toward the bottom-right. Since both paths must eventually reach the destination, we track their positions using four variables (r1, c1) and (r2, c2). At each step, both move either down or right, giving us 4 combinations of moves. When they land on the same cell, we only count the cherries once to avoid double counting.
dfs(r1, c1, r2, c2) that returns the maximum cherries collected when person 1 is at (r1, c1) and person 2 is at (r2, c2).-1), return a large negative value to invalidate this path.(n-1, n-1), return the cherry value at that cell.0 (no valid path exists).class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
n = len(grid)
def dfs(r1, c1, r2, c2):
if r1 >= n or c1 >= n or r2 >= n or c2 >= n or grid[r1][c1] == -1 or grid[r2][c2] == -1:
return -1000
if r1 == n - 1 and r2 == n - 1 and c1 == n - 1 and c2 == n - 1:
return grid[r1][c1]
res = dfs(r1 + 1, c1, r2 + 1, c2)
res = max(res, dfs(r1 + 1, c1, r2, c2 + 1))
res = max(res, dfs(r1, c1 + 1, r2 + 1, c2))
res = max(res, dfs(r1, c1 + 1, r2, c2 + 1))
res += grid[r1][c1] + grid[r2][c2]
res -= (grid[r1][c1] if (r1 == r2 and c1 == c2) else 0)
return res
return max(0, dfs(0, 0, 0, 0))The recursive solution has overlapping subproblems since the same combination of positions can be reached through different paths. By storing the results of each state (r1, c1, r2, c2) in a 4D memoization table, we avoid redundant calculations. This transforms the exponential time complexity into polynomial time.
dfs(r1, c1, r2, c2) as before.class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
n = len(grid)
dp = [[[[float("-inf")] * n for _ in range(n)] for _ in range(n)] for _ in range(n)]
def dfs(r1, c1, r2, c2):
if r1 >= n or c1 >= n or r2 >= n or c2 >= n or grid[r1][c1] == -1 or grid[r2][c2] == -1:
return -1000
if r1 == n - 1 and r2 == n - 1 and c1 == n - 1 and c2 == n - 1:
return grid[r1][c1]
if dp[r1][c1][r2][c2] != float("-inf"):
return dp[r1][c1][r2][c2]
res = dfs(r1 + 1, c1, r2 + 1, c2)
res = max(res, dfs(r1 + 1, c1, r2, c2 + 1))
res = max(res, dfs(r1, c1 + 1, r2 + 1, c2))
res = max(res, dfs(r1, c1 + 1, r2, c2 + 1))
res += grid[r1][c1] + grid[r2][c2]
if r1 == r2 and c1 == c2:
res -= grid[r1][c1]
dp[r1][c1][r2][c2] = res
return res
return max(0, dfs(0, 0, 0, 0))We can reduce the state space by observing that both people always take the same number of steps. If person 1 is at (r1, c1), then they have taken r1 + c1 steps. Since person 2 has also taken the same number of steps, if we know r2, we can derive c2 as (r1 + c1 - r2). This reduces our state from 4 dimensions to 3 dimensions.
(r1, c1, r2) as state variables.c2 dynamically as c2 = r1 + c1 - r2.c2.(n-1, n-1), return the cherry value.class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
n = len(grid)
dp = [[[float("-inf")] * n for _ in range(n)] for _ in range(n)]
def dfs(r1, c1, r2):
c2 = r1 + c1 - r2
if r1 >= n or c1 >= n or r2 >= n or c2 >= n or grid[r1][c1] == -1 or grid[r2][c2] == -1:
return -1000
if r1 == n - 1 and c1 == n - 1:
return grid[r1][c1]
if dp[r1][c1][r2] != float("-inf"):
return dp[r1][c1][r2]
res = dfs(r1 + 1, c1, r2 + 1)
res = max(res, dfs(r1 + 1, c1, r2))
res = max(res, dfs(r1, c1 + 1, r2 + 1))
res = max(res, dfs(r1, c1 + 1, r2))
res += grid[r1][c1]
if (r1, c1) != (r2, c2):
res += grid[r2][c2]
dp[r1][c1][r2] = res
return res
return max(0, dfs(0, 0, 0))Instead of recursion with memoization, we can fill the DP table iteratively starting from the destination and working backward to the start. We iterate through all valid combinations of (r1, c1, r2) in reverse order, computing c2 from the constraint, and build up the solution from smaller subproblems.
[n][n][n].(n-1, n-1, n-1) down to (0, 0, 0) for (r1, c1, r2).c2 = r1 + c1 - r2. Skip if c2 is out of bounds.dp[0][0][0], clamped to 0 if negative.class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
n = len(grid)
dp = [[[float("-inf")] * n for _ in range(n)] for _ in range(n)]
for r1 in reversed(range(n)):
for c1 in reversed(range(n)):
for r2 in reversed(range(n)):
c2 = r1 + c1 - r2
if c2 < 0 or c2 >= n:
continue
if grid[r1][c1] == -1 or grid[r2][c2] == -1:
continue
if r1 == n - 1 and c1 == n - 1:
dp[r1][c1][r2] = grid[r1][c1]
else:
res = max(
dp[r1 + 1][c1][r2 + 1] if r1 + 1 < n and r2 + 1 < n else -1000,
dp[r1 + 1][c1][r2] if r1 + 1 < n else -1000,
dp[r1][c1 + 1][r2 + 1] if c1 + 1 < n and r2 + 1 < n else -1000,
dp[r1][c1 + 1][r2] if c1 + 1 < n else -1000
)
if res == -1000:
continue
res += grid[r1][c1]
if (r1, c1) != (r2, c2):
res += grid[r2][c2]
dp[r1][c1][r2] = res
return max(0, dp[0][0][0])Since we process states by the total number of steps k = r1 + c1 = r2 + c2, and each state only depends on states from the previous step count, we only need to keep track of the previous layer. This reduces space from O(n^3) to O(n^2) by using two 2D arrays that alternate between the current and previous step.
prev array of size [n][n] representing states for the previous step count.prev[0][0] with the starting cell's cherry value.k from 1 to 2n-2:dp array for the current step.(r1, r2) pair where c1 = k - r1 and c2 = k - r2 are within bounds:prev = dp for the next iteration.prev[n-1][n-1], clamped to 0 if negative.class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
n = len(grid)
prev = [[float("-inf")] * n for _ in range(n)]
prev[0][0] = grid[0][0]
for k in range(1, 2 * n - 1):
dp = [[float("-inf")] * n for _ in range(n)]
for r1 in range(max(0, k - (n - 1)), min(n, k + 1)):
c1 = k - r1
if c1 >= n or grid[r1][c1] == -1:
continue
for r2 in range(max(0, k - (n - 1)), min(n, k + 1)):
c2 = k - r2
if c2 >= n or grid[r2][c2] == -1:
continue
val = prev[r1][r2]
if r1 > 0: val = max(val, prev[r1 - 1][r2])
if r2 > 0: val = max(val, prev[r1][r2 - 1])
if r1 > 0 and r2 > 0: val = max(val, prev[r1 - 1][r2 - 1])
if val < 0: continue
val += grid[r1][c1]
if r1 != r2: val += grid[r2][c2]
dp[r1][r2] = val
prev = dp
return max(0, prev[n - 1][n - 1])Running a greedy or DP solution for the forward path, removing collected cherries, then solving the return path separately. This approach fails because the optimal combined solution may require a suboptimal forward path to enable a better return path. Both paths must be considered simultaneously.
Forgetting to subtract cherries when both simulated people land on the same cell. If (r1, c1) == (r2, c2), you must count cherries only once.
# Wrong: always adds both
res += grid[r1][c1] + grid[r2][c2]
# Correct: subtract overlap
if r1 == r2 and c1 == c2:
res -= grid[r1][c1]Returning 0 instead of a large negative value when hitting a thorn. Returning 0 makes invalid paths appear viable when taking the maximum. Return -1000 or float('-inf') to ensure invalid paths are never selected.
Not checking if the final result is negative before returning. If no valid path exists (blocked by thorns), the algorithm may return a negative number. The answer should be max(0, result).
Not recognizing that c2 can be derived from c1, r1, and r2 since both people take the same number of steps (r1 + c1 == r2 + c2). This optimization reduces state space from O(n^4) to O(n^3).