The Cherry Pickup problem involves a square grid represented as a 2D array called grid
, where each cell may contain:
0
: an empty cell,1
: a cell with a cherry,-1
: a cell with a thorn (impassable).(0,0)
and move to the bottom-right corner (N-1,N-1)
, moving only right or down at each step.(N-1,N-1)
back to (0,0)
, moving only left or up at each step.-1
). If a cell contains a cherry, you pick it up (the cell becomes empty after picking up). You cannot pick up the same cherry twice. If there is no valid path for either trip, you should return 0
.
At first glance, the problem seems to ask for two separate traversals: one forward and one backward, each maximizing the cherries collected. A brute-force approach would try all possible paths for both trips, but this quickly becomes infeasible due to the exponential number of possible routes.
A key insight is that the two trips can be modeled as two people starting at (0,0)
and both moving to (N-1,N-1)
at the same time, each taking a step either down or right. This is because the return trip is simply a reverse traversal, and collecting cherries on the way back is equivalent to collecting them in a second simultaneous path. This reframing allows us to use dynamic programming to efficiently keep track of the maximum cherries collected, making sure not to double-count cherries picked up by both "travellers" if they land on the same cell.
The challenge then becomes designing a dynamic programming state that efficiently tracks both positions and avoids redundant calculations.
To solve this problem efficiently, we use dynamic programming (DP) to simulate two people moving from the top-left to the bottom-right of the grid at the same time.
Step-by-step Algorithm:
(0,0)
and moving to (N-1,N-1)
simultaneously.dp[r1][c1][r2]
represent the maximum cherries collected when:
(r1, c1)
(r2, c2)
, where c2 = r1 + c1 - r2
(since both have taken the same number of steps)(N-1, N-1)
, return the value in that cell (if not a thorn).0
if no valid path exists.Sample Input:
grid = [ [0, 1, -1], [1, 0, -1], [1, 1, 1] ]
(0,0)
(no cherry).(0,2)
; so, only safe moves are down or right to non-thorn cells.(0,1)
, (1,0)
, (2,0)
, (2,1)
, and (2,2)
.Brute-force Approach:
O(4^{2N})
paths.O(N^3)
, as the DP state is defined by three variables (r1
, c1
, r2
), each ranging from 0
to N-1
.O(N^3)
, for memoization storage of all possible states.N
, making it feasible for reasonable grid sizes (typically N <= 50
).
The Cherry Pickup problem can be efficiently solved by modeling both trips as two synchronized journeys from the top-left to the bottom-right of the grid. Using a dynamic programming approach with memoization, we systematically explore all valid paths, maximize cherry collection, and avoid double-counting. The cubic state space ensures efficiency, and the solution elegantly handles the challenge of collecting each cherry only once.
from functools import lru_cache
class Solution:
def cherryPickup(self, grid):
N = len(grid)
@lru_cache(None)
def dp(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 float('-inf')
if r1 == c1 == N-1:
return grid[r1][c1]
if r2 == c2 == N-1:
return grid[r2][c2]
cherries = grid[r1][c1]
if (r1, c1) != (r2, c2):
cherries += grid[r2][c2]
next_best = max(
dp(r1+1, c1, r2+1),
dp(r1, c1+1, r2),
dp(r1+1, c1, r2),
dp(r1, c1+1, r2+1)
)
return cherries + next_best
result = dp(0, 0, 0)
return max(0, result)
class Solution {
public:
int cherryPickup(vector<vector<int>>& grid) {
int N = grid.size();
vector<vector<vector<int>>> memo(N, vector<vector<int>>(N, vector<int>(N, INT_MIN)));
function<int(int,int,int)> dp = [&](int r1, int c1, int r2) {
int c2 = r1 + c1 - r2;
if (r1 >= N || c1 >= N || r2 >= N || c2 >= N ||
grid[r1][c1] == -1 || grid[r2][c2] == -1)
return INT_MIN;
if (r1 == N-1 && c1 == N-1)
return grid[r1][c1];
if (r2 == N-1 && c2 == N-1)
return grid[r2][c2];
if (memo[r1][c1][r2] != INT_MIN)
return memo[r1][c1][r2];
int cherries = grid[r1][c1];
if (r1 != r2 || c1 != c2)
cherries += grid[r2][c2];
int next = max({
dp(r1+1, c1, r2+1),
dp(r1, c1+1, r2),
dp(r1+1, c1, r2),
dp(r1, c1+1, r2+1)
});
memo[r1][c1][r2] = cherries + next;
return memo[r1][c1][r2];
};
int res = dp(0, 0, 0);
return max(0, res);
}
};
class Solution {
public int cherryPickup(int[][] grid) {
int N = grid.length;
Integer[][][] memo = new Integer[N][N][N];
return Math.max(0, dp(grid, 0, 0, 0, memo));
}
private int dp(int[][] grid, int r1, int c1, int r2, Integer[][][] memo) {
int N = grid.length;
int c2 = r1 + c1 - r2;
if (r1 >= N || c1 >= N || r2 >= N || c2 >= N ||
grid[r1][c1] == -1 || grid[r2][c2] == -1)
return Integer.MIN_VALUE;
if (r1 == N-1 && c1 == N-1)
return grid[r1][c1];
if (r2 == N-1 && c2 == N-1)
return grid[r2][c2];
if (memo[r1][c1][r2] != null)
return memo[r1][c1][r2];
int cherries = grid[r1][c1];
if (r1 != r2 || c1 != c2)
cherries += grid[r2][c2];
int next = Math.max(
Math.max(dp(grid, r1+1, c1, r2+1, memo), dp(grid, r1, c1+1, r2, memo)),
Math.max(dp(grid, r1+1, c1, r2, memo), dp(grid, r1, c1+1, r2+1, memo))
);
memo[r1][c1][r2] = cherries + next;
return memo[r1][c1][r2];
}
}
var cherryPickup = function(grid) {
const N = grid.length;
const memo = new Map();
function dp(r1, c1, r2) {
let c2 = r1 + c1 - r2;
let key = r1 + ',' + c1 + ',' + r2;
if (r1 >= N || c1 >= N || r2 >= N || c2 >= N ||
grid[r1][c1] === -1 || grid[r2][c2] === -1)
return -Infinity;
if (r1 === N-1 && c1 === N-1)
return grid[r1][c1];
if (r2 === N-1 && c2 === N-1)
return grid[r2][c2];
if (memo.has(key)) return memo.get(key);
let cherries = grid[r1][c1];
if (r1 !== r2 || c1 !== c2)
cherries += grid[r2][c2];
let next = Math.max(
dp(r1+1, c1, r2+1),
dp(r1, c1+1, r2),
dp(r1+1, c1, r2),
dp(r1, c1+1, r2+1)
);
memo.set(key, cherries + next);
return cherries + next;
}
let res = dp(0,0,0);
return Math.max(0, res);
};