Facebook Pixel

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:

  1. Start at position (0, 0) and travel to position (n-1, n-1) by only moving right or down through valid cells (cells with value 0 or 1)
  2. From (n-1, n-1), return back to (0, 0) by only moving left or up through valid cells
  3. When you pass through a cell containing a cherry (1), you pick it up and the cell becomes empty (0)
  4. If no valid path exists between (0, 0) and (n-1, n-1), return 0

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).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 taken k 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:

  1. Iterate through all possible positions (i1, j1) for person 1 where j1 = k - i1
  2. Iterate through all possible positions (i2, j2) for person 2 where j2 = k - i2
  3. Check validity:
    • Both j1 and j2 must be within bounds [0, n)
    • Neither grid[i1][j1] nor grid[i2][j2] should be -1 (thorn)
  4. 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]
  5. Consider all valid previous positions:
    • Person 1 could have come from (i1-1, j1) or (i1, j1-1), meaning x1 ∈ {i1-1, i1}
    • Person 2 could have come from (i2-1, j2) or (i2, j2-1), meaning x2 ∈ {i2-1, i2}
    • Update: f[k][i1][i2] = max(f[k][i1][i2], f[k-1][x1][x2] + t)

Final Answer:

  • The maximum cherries when both reach (n-1, n-1) is f[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 Evaluator

Example 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)

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 or f[1][1][1] = 1
    • Current cherry: grid[1][1] = 0 (empty)
    • f[2][1][1] = max(2+0, 1+0) = 2
  • 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

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 or f[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

Step 4 (k=4): Both reach (2,2)

  • Previous best paths lead here from f[3][1][2] = 5 or f[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 from 1 to 2n - 2, which is O(n) iterations
  • The second loop iterates through i1 from 0 to n - 1, which is O(n) iterations
  • The third loop iterates through i2 from 0 to n - 1, which is O(n) iterations
  • Inside these three loops, there are two more nested loops for x1 and x2, but these only iterate through at most 2 values each (from i - 1 to i), making them O(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 is O(n)
  • Second dimension: n elements, which is O(n)
  • Third dimension: n elements, which is O(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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More