741. Cherry Pickup
Problem Description
You have an n x n
grid representing a cherry field where each cell contains one of three values:
0
: empty cell (passable)1
: cell with a cherry (you can pick it and pass through)-1
: cell with a thorn (blocks movement)
Your task is to find the maximum number of cherries you can collect following these rules:
- Start at position
(0, 0)
and travel to position(n-1, n-1)
by only moving right or down through valid cells (cells with value0
or1
) - From
(n-1, n-1)
, return back to(0, 0)
by only moving left or up through valid cells - When you pass through a cell containing a cherry (
1
), you pick it up and the cell becomes empty (0
) - If no valid path exists between
(0, 0)
and(n-1, n-1)
, return0
The key insight in the solution is to reframe the problem: instead of thinking about one trip from (0, 0)
to (n-1, n-1)
and back, we can think of it as two people simultaneously traveling from (0, 0)
to (n-1, n-1)
. Both people move at the same pace (taking the same number of steps), and when they occupy the same cell, they only collect the cherry once.
The dynamic programming approach uses f[k][i1][i2]
to represent the maximum cherries collected when:
- Both people have taken exactly
k
steps - Person 1 is at position
(i1, k-i1)
- Person 2 is at position
(i2, k-i2)
The state transition considers all valid previous positions for both people, and the final answer is max(0, f[2n-2][n-1][n-1])
, where 2n-2
is the total number of steps needed to go from (0, 0)
to (n-1, n-1)
.
Intuition
The naive approach might be to find the optimal path from (0, 0)
to (n-1, n-1)
, collect cherries along the way, then find the optimal return path on the modified grid. However, this greedy approach fails because the first path might block better options for the return journey.
The key insight is recognizing that going from (0, 0)
to (n-1, n-1)
and back is equivalent to having two people start simultaneously from (0, 0)
and both reach (n-1, n-1)
. Why? Because any path from start to end and back can be split into two paths from start to end - one person follows the original forward path, and another follows the return path in reverse.
Since both people move simultaneously, after k
steps, if person 1 is at (i1, j1)
and person 2 is at (i2, j2)
, we know that i1 + j1 = k
and i2 + j2 = k
. This means we only need to track the row positions i1
and i2
, as the column positions can be derived: j1 = k - i1
and j2 = k - i2
.
This transforms our problem into a 3-dimensional DP: f[k][i1][i2]
represents the maximum cherries collected when both people have taken k
steps, with person 1 at row i1
and person 2 at row i2
.
At each step, both people can move either right or down (equivalent to coming from left or up in the previous step). This gives us four possible combinations of previous positions to consider. When both people land on the same cell, we count that cherry only once.
The total number of steps to reach (n-1, n-1)
from (0, 0)
is 2n-2
(we need to move n-1
steps right and n-1
steps down). The final answer is the maximum cherries collected when both people reach (n-1, n-1)
after 2n-2
steps.
Learn more about Dynamic Programming patterns.
Solution Approach
The implementation uses dynamic programming with three dimensions. Let's walk through the key components:
State Definition:
f[k][i1][i2]
represents the maximum cherries collected when both persons have takenk
steps total- Person 1 is at position
(i1, k-i1)
- Person 2 is at position
(i2, k-i2)
Initialization:
- Create a 3D array with dimensions
(2n-1) × n × n
- Initialize all values to negative infinity (
-inf
) to represent invalid states - Set
f[0][0][0] = grid[0][0]
as both persons start at(0, 0)
State Transition:
For each step k
from 1 to 2n-2
:
- Iterate through all possible positions
(i1, j1)
for person 1 wherej1 = k - i1
- Iterate through all possible positions
(i2, j2)
for person 2 wherej2 = k - i2
- Check validity:
- Both
j1
andj2
must be within bounds[0, n)
- Neither
grid[i1][j1]
norgrid[i2][j2]
should be-1
(thorn)
- Both
- Calculate cherries collected at current positions:
- If both persons are at the same cell (
i1 == i2
), count cherry once:t = grid[i1][j1]
- If at different cells, sum both:
t = grid[i1][j1] + grid[i2][j2]
- If both persons are at the same cell (
- Consider all valid previous positions:
- Person 1 could have come from
(i1-1, j1)
or(i1, j1-1)
, meaningx1 ∈ {i1-1, i1}
- Person 2 could have come from
(i2-1, j2)
or(i2, j2-1)
, meaningx2 ∈ {i2-1, i2}
- Update:
f[k][i1][i2] = max(f[k][i1][i2], f[k-1][x1][x2] + t)
- Person 1 could have come from
Final Answer:
- The maximum cherries when both reach
(n-1, n-1)
isf[2n-2][n-1][n-1]
- Return
max(0, f[2n-2][n-1][n-1])
to handle cases where no valid path exists (result would be negative infinity)
The time complexity is O(n³)
as we have three nested loops each running up to n
times, and the space complexity is also O(n³)
for the 3D DP array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider this 3×3 grid:
grid = [[0, 1, -1], [1, 0, 1], [1, 1, 1]]
We'll think of this as two people simultaneously moving from (0,0) to (2,2). Each person can only move right or down.
Step 0 (k=0): Both start at (0,0)
- Position: Person 1 at (0,0), Person 2 at (0,0)
- Cherries collected: 0 (empty cell)
f[0][0][0] = 0
Step 1 (k=1): Each person takes 1 step
- Possible positions after 1 step: (0,1) or (1,0)
- Valid states:
- Both at (0,1):
f[1][0][0] = 0 + 1 = 1
(one cherry at grid[0][1]) - Both at (1,0):
f[1][1][1] = 0 + 1 = 1
(one cherry at grid[1][0]) - One at (0,1), one at (1,0):
f[1][0][1] = 0 + 1 + 1 = 2
(two different cherries)
- Both at (0,1):
Step 2 (k=2): Each person has taken 2 steps total
- For Person 1 at (0,2), Person 2 at (1,1): Can't reach (0,2) due to thorn at grid[0][2]=-1
- For Person 1 at (1,1), Person 2 at (1,1) (same cell):
- Previous state:
f[1][0][1] = 2
orf[1][1][1] = 1
- Current cherry: grid[1][1] = 0 (empty)
f[2][1][1] = max(2+0, 1+0) = 2
- Previous state:
- For Person 1 at (2,0), Person 2 at (1,1):
- Previous state:
f[1][1][1] = 1
- Current cherries: grid[2][0] + grid[1][1] = 1 + 0 = 1
f[2][2][1] = 1 + 1 = 2
- Previous state:
Step 3 (k=3): Each person has taken 3 steps
- For Person 1 at (1,2), Person 2 at (2,1):
- Best previous:
f[2][1][1] = 2
orf[2][1][2] = 3
- Current cherries: grid[1][2] + grid[2][1] = 1 + 1 = 2
f[3][1][2] = max(2+2, 3+2) = 5
- Best previous:
Step 4 (k=4): Both reach (2,2)
- Previous best paths lead here from
f[3][1][2] = 5
orf[3][2][1] = 5
- Current cherry: grid[2][2] = 1 (both at same cell, count once)
f[4][2][2] = 5 + 1 = 6
Final Answer: max(0, f[4][2][2]) = 6
This means we can collect a maximum of 6 cherries. One optimal solution:
- Person 1 path: (0,0) → (0,1) → (1,1) → (1,2) → (2,2)
- Person 2 path: (0,0) → (1,0) → (1,1) → (2,1) → (2,2)
- Cherries: 1 at (0,1), 1 at (1,0), 1 at (1,2), 1 at (2,1), 1 at (2,2) = 5 total
Wait, let me recalculate more carefully:
Actually, the optimal paths would be:
- Person 1: (0,0) → (0,1) → (1,1) → (2,1) → (2,2)
- Person 2: (0,0) → (1,0) → (2,0) → (2,1) → (2,2)
Collecting cherries at: (0,1)=1, (1,0)=1, (2,0)=1, (2,1)=1, (2,2)=1 = 5 cherries total.
The DP tracks all possible combinations and finds the maximum cherries collectible is 5.
Solution Implementation
1class Solution:
2 def cherryPickup(self, grid: List[List[int]]) -> int:
3 n = len(grid)
4
5 # dp[k][i1][i2] represents the maximum cherries collected when:
6 # - Person 1 is at position (i1, j1) where j1 = k - i1
7 # - Person 2 is at position (i2, j2) where j2 = k - i2
8 # - k represents the sum of coordinates (i + j), indicating the step number
9 dp = [[[-float('inf')] * n for _ in range(n)] for _ in range(2 * n - 1)]
10
11 # Initialize starting position - both persons start at (0, 0)
12 dp[0][0][0] = grid[0][0]
13
14 # Iterate through each step (k represents Manhattan distance from origin)
15 for k in range(1, 2 * n - 1):
16 for row1 in range(n):
17 for row2 in range(n):
18 # Calculate column positions based on current step and row
19 col1 = k - row1
20 col2 = k - row2
21
22 # Check if positions are valid and not blocked by thorns
23 if (not 0 <= col1 < n or
24 not 0 <= col2 < n or
25 grid[row1][col1] == -1 or
26 grid[row2][col2] == -1):
27 continue
28
29 # Calculate cherries to pick at current positions
30 cherries_picked = grid[row1][col1]
31 # Avoid double counting if both persons are at the same cell
32 if row1 != row2:
33 cherries_picked += grid[row2][col2]
34
35 # Check all possible previous positions for both persons
36 # Each person could have come from either up or left
37 for prev_row1 in range(row1 - 1, row1 + 1):
38 for prev_row2 in range(row2 - 1, row2 + 1):
39 # Ensure previous positions are within bounds
40 if prev_row1 >= 0 and prev_row2 >= 0:
41 dp[k][row1][row2] = max(
42 dp[k][row1][row2],
43 dp[k - 1][prev_row1][prev_row2] + cherries_picked
44 )
45
46 # Return the maximum cherries collected, or 0 if no valid path exists
47 return max(0, dp[-1][-1][-1])
48
1class Solution {
2 public int cherryPickup(int[][] grid) {
3 int n = grid.length;
4
5 // dp[step][row1][row2] represents the maximum cherries collected
6 // when person 1 is at (row1, col1) and person 2 is at (row2, col2)
7 // where col1 = step - row1 and col2 = step - row2
8 int[][][] dp = new int[n * 2][n][n];
9
10 // Initialize starting position
11 dp[0][0][0] = grid[0][0];
12
13 // Iterate through all possible steps (Manhattan distance from origin)
14 for (int step = 1; step < n * 2 - 1; step++) {
15 // Try all possible positions for person 1
16 for (int row1 = 0; row1 < n; row1++) {
17 // Try all possible positions for person 2
18 for (int row2 = 0; row2 < n; row2++) {
19 // Calculate column positions based on current step
20 int col1 = step - row1;
21 int col2 = step - row2;
22
23 // Initialize current state as invalid
24 dp[step][row1][row2] = Integer.MIN_VALUE;
25
26 // Check if positions are valid (within bounds and not thorns)
27 if (col1 < 0 || col1 >= n || col2 < 0 || col2 >= n ||
28 grid[row1][col1] == -1 || grid[row2][col2] == -1) {
29 continue;
30 }
31
32 // Calculate cherries collected at current positions
33 int currentCherries = grid[row1][col1];
34 // Avoid double counting if both persons are at the same cell
35 if (row1 != row2) {
36 currentCherries += grid[row2][col2];
37 }
38
39 // Check all possible previous positions for person 1
40 // Person 1 could have come from (row1-1, col1) or (row1, col1-1)
41 for (int prevRow1 = row1 - 1; prevRow1 <= row1; prevRow1++) {
42 // Check all possible previous positions for person 2
43 // Person 2 could have come from (row2-1, col2) or (row2, col2-1)
44 for (int prevRow2 = row2 - 1; prevRow2 <= row2; prevRow2++) {
45 // Ensure previous positions are within bounds
46 if (prevRow1 >= 0 && prevRow2 >= 0) {
47 dp[step][row1][row2] = Math.max(
48 dp[step][row1][row2],
49 dp[step - 1][prevRow1][prevRow2] + currentCherries
50 );
51 }
52 }
53 }
54 }
55 }
56 }
57
58 // Return the maximum cherries collected when both persons reach (n-1, n-1)
59 // Return 0 if no valid path exists
60 return Math.max(0, dp[n * 2 - 2][n - 1][n - 1]);
61 }
62}
63
1class Solution {
2public:
3 int cherryPickup(vector<vector<int>>& grid) {
4 int n = grid.size();
5
6 // dp[step][row1][row2] represents the maximum cherries collected
7 // when two people are at positions (row1, step-row1) and (row2, step-row2)
8 // step = row + col (Manhattan distance from origin)
9 vector<vector<vector<int>>> dp(2 * n - 1, vector<vector<int>>(n, vector<int>(n, -1e9)));
10
11 // Initialize: both people start at (0, 0)
12 dp[0][0][0] = grid[0][0];
13
14 // Iterate through all possible steps (Manhattan distance from origin)
15 for (int step = 1; step < 2 * n - 1; ++step) {
16 // Try all possible positions for person 1
17 for (int row1 = 0; row1 < n; ++row1) {
18 // Try all possible positions for person 2
19 for (int row2 = 0; row2 < n; ++row2) {
20 // Calculate column positions based on current step and row
21 int col1 = step - row1;
22 int col2 = step - row2;
23
24 // Skip invalid positions (out of bounds or blocked cells)
25 if (col1 < 0 || col1 >= n || col2 < 0 || col2 >= n ||
26 grid[row1][col1] == -1 || grid[row2][col2] == -1) {
27 continue;
28 }
29
30 // Calculate cherries collected at current positions
31 int cherries = grid[row1][col1];
32 // Avoid double counting if both people are at the same position
33 if (row1 != row2) {
34 cherries += grid[row2][col2];
35 }
36
37 // Check all possible previous positions for person 1
38 // Person 1 could have come from (row1-1, col1) or (row1, col1-1)
39 for (int prevRow1 = row1 - 1; prevRow1 <= row1; ++prevRow1) {
40 // Check all possible previous positions for person 2
41 // Person 2 could have come from (row2-1, col2) or (row2, col2-1)
42 for (int prevRow2 = row2 - 1; prevRow2 <= row2; ++prevRow2) {
43 // Ensure previous positions are valid
44 if (prevRow1 >= 0 && prevRow2 >= 0) {
45 dp[step][row1][row2] = max(dp[step][row1][row2],
46 dp[step - 1][prevRow1][prevRow2] + cherries);
47 }
48 }
49 }
50 }
51 }
52 }
53
54 // Return the maximum cherries collected when both people reach (n-1, n-1)
55 // If no valid path exists, return 0
56 return max(0, dp[2 * n - 2][n - 1][n - 1]);
57 }
58};
59
1/**
2 * Solves the Cherry Pickup problem using dynamic programming.
3 * Two persons start from (0,0) and need to reach (n-1,n-1), picking up maximum cherries.
4 *
5 * @param grid - 2D array where 0 = empty cell, 1 = cherry, -1 = thorn (blocked)
6 * @returns Maximum number of cherries that can be collected
7 */
8function cherryPickup(grid: number[][]): number {
9 const gridSize: number = grid.length;
10
11 // dp[step][row1][row2] represents max cherries collected when:
12 // - Both persons have moved 'step' steps total
13 // - Person 1 is at row 'row1', Person 2 is at row 'row2'
14 // - Their columns are calculated as: col = step - row
15 const dp: number[][][] = Array.from(
16 { length: gridSize * 2 - 1 },
17 () => Array.from(
18 { length: gridSize },
19 () => Array.from(
20 { length: gridSize },
21 () => -Infinity
22 )
23 )
24 );
25
26 // Initialize starting position
27 dp[0][0][0] = grid[0][0];
28
29 // Process each step from 1 to (2n - 2)
30 for (let step = 1; step < gridSize * 2 - 1; ++step) {
31 // Try all possible positions for person 1
32 for (let row1 = 0; row1 < gridSize; ++row1) {
33 // Try all possible positions for person 2
34 for (let row2 = 0; row2 < gridSize; ++row2) {
35 // Calculate column positions based on current step
36 const col1: number = step - row1;
37 const col2: number = step - row2;
38
39 // Check if positions are valid
40 if (col1 < 0 || col1 >= gridSize ||
41 col2 < 0 || col2 >= gridSize ||
42 grid[row1][col1] === -1 ||
43 grid[row2][col2] === -1) {
44 continue;
45 }
46
47 // Calculate cherries picked at current positions
48 // Avoid double counting when both persons are at same position
49 const cherriesAtCurrentStep: number = grid[row1][col1] +
50 (row1 !== row2 ? grid[row2][col2] : 0);
51
52 // Check all possible previous positions
53 // Person can come from either up (row-1) or left (row stays same)
54 for (let prevRow1 = row1 - 1; prevRow1 <= row1; ++prevRow1) {
55 for (let prevRow2 = row2 - 1; prevRow2 <= row2; ++prevRow2) {
56 // Ensure previous positions are valid
57 if (prevRow1 >= 0 && prevRow2 >= 0) {
58 dp[step][row1][row2] = Math.max(
59 dp[step][row1][row2],
60 dp[step - 1][prevRow1][prevRow2] + cherriesAtCurrentStep
61 );
62 }
63 }
64 }
65 }
66 }
67 }
68
69 // Return the maximum cherries collected, or 0 if no valid path exists
70 return Math.max(0, dp[gridSize * 2 - 2][gridSize - 1][gridSize - 1]);
71}
72
Time and Space Complexity
Time Complexity: O(n^3)
The algorithm uses dynamic programming with three nested loops:
- The outermost loop iterates through
k
from1
to2n - 2
, which isO(n)
iterations - The second loop iterates through
i1
from0
ton - 1
, which isO(n)
iterations - The third loop iterates through
i2
from0
ton - 1
, which isO(n)
iterations - Inside these three loops, there are two more nested loops for
x1
andx2
, but these only iterate through at most 2 values each (fromi - 1
toi
), making themO(1)
Therefore, the overall time complexity is O(n) × O(n) × O(n) × O(1) = O(n^3)
.
Space Complexity: O(n^3)
The algorithm creates a 3-dimensional DP array f
with dimensions:
- First dimension:
(2n - 1)
elements, which isO(n)
- Second dimension:
n
elements, which isO(n)
- Third dimension:
n
elements, which isO(n)
The total space used by the DP array is O(n) × O(n) × O(n) = O(n^3)
.
Where n
is the side length of the grid.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Cherry Counting When Persons Meet
One of the most common mistakes is forgetting to handle the case when both persons occupy the same cell. If you simply add grid[i1][j1] + grid[i2][j2]
without checking if i1 == i2
(which implies j1 == j2
when k
is the same), you'll double-count the cherry at that position.
Wrong approach:
cherries_picked = grid[row1][col1] + grid[row2][col2] # Always adds both
Correct approach:
cherries_picked = grid[row1][col1] if row1 != row2: # Only add second person's cherry if at different positions cherries_picked += grid[row2][col2]
2. Incorrect Previous Position Iteration
Another frequent error is incorrectly determining which previous positions to check. The code uses range(row1 - 1, row1 + 1)
which generates [row1-1, row1]
. Some developers might mistakenly think this should be range(row1, row1 + 2)
or forget that persons can only come from up (row-1) or left (same row, col-1).
Wrong approach:
for prev_row1 in range(row1, row1 + 2): # This would check row1 and row1+1
# ...
Correct approach:
for prev_row1 in range(row1 - 1, row1 + 1): # Checks row1-1 and row1
# ...
3. Forgetting to Initialize with Negative Infinity
Initializing the DP array with 0 instead of negative infinity can lead to incorrect results. Using negative infinity ensures that invalid paths (those passing through thorns or going out of bounds) don't contribute to the maximum calculation.
Wrong approach:
dp = [[[0] * n for _ in range(n)] for _ in range(2 * n - 1)] # Wrong!
Correct approach:
dp = [[[-float('inf')] * n for _ in range(n)] for _ in range(2 * n - 1)]
4. Misunderstanding the Step Count
The total number of steps from (0,0)
to (n-1,n-1)
is 2n-2
, not 2n-1
or n-1
. This is because we need exactly n-1
right moves and n-1
down moves. Some might incorrectly set the loop range or array size.
Wrong approach:
for k in range(1, 2 * n): # Goes one step too far
Correct approach:
for k in range(1, 2 * n - 1): # Correct: total steps is 2n-2
5. Not Handling the "No Valid Path" Case
If there's no valid path from start to end (due to thorns blocking all routes), the DP value will remain negative infinity. Returning this directly would give an incorrect result. Always use max(0, dp[-1][-1][-1])
to handle this case.
Wrong approach:
return dp[-1][-1][-1] # Could return -inf if no path exists
Correct approach:
return max(0, dp[-1][-1][-1]) # Returns 0 if no valid path
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!