1463. Cherry Pickup II - Explanation
Prerequisites
Before attempting this problem, you should be comfortable with:
- Dynamic Programming - Using memoization or tabulation to solve optimization problems with overlapping subproblems
- 2D/3D Arrays - Working with multi-dimensional data structures for state representation
- Recursion with Multiple Variables - Tracking multiple entities (two robots) simultaneously through recursive state exploration
1. Recursion
Intuition
Two robots start at the top corners of the grid and move down simultaneously, one row at a time. At each row, each robot can move diagonally left, straight down, or diagonally right. We track both robot positions and explore all 9 combinations of moves (3 choices for robot 1 times 3 choices for robot 2). If both robots land on the same cell, we only count the cherries once. We enforce c1 <= c2 to avoid duplicate states since the robots are interchangeable.
Algorithm
- Define a recursive function
dfs(r, c1, c2)whereris the current row,c1is robot 1's column, andc2is robot 2's column. - Base case: If
c1orc2is out of bounds, orc1 > c2, return0. - Base case: If
requalsROWS - 1, return the sum of cherries at both positions (counting once if they overlap). - For each of the 9 direction combinations (
c1_d,c2_din[-1, 0, 1]):- Recursively call
dfs(r + 1, c1 + c1_d, c2 + c2_d). - Track the maximum result.
- Recursively call
- Add cherries from current positions to the result and return.
class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
def dfs(r, c1, c2):
if c1 < 0 or c2 < 0 or c1 >= COLS or c2 >= COLS or c1 > c2:
return 0
if r == ROWS - 1:
return grid[r][c1] + (grid[r][c2] if c1 != c2 else 0)
res = 0
for c1_d in [-1, 0, 1]:
for c2_d in [-1, 0, 1]:
res = max(res, dfs(r + 1, c1 + c1_d, c2 + c2_d))
return res + grid[r][c1] + (grid[r][c2] if c1 != c2 else 0)
return dfs(0, 0, COLS - 1)Time & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
Where is the number of rows and is the number of columns in the grid.
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution recomputes the same states multiple times. Since the state is defined by (r, c1, c2), we can use a 3D cache to store results for previously visited states. When we encounter a state we have already solved, we return the cached value instead of recomputing it.
Algorithm
- Create a 3D cache array of size
[ROWS][COLS][COLS]initialized to-1. - In the recursive function, first check if the state is already in the cache. If so, return the cached value.
- Perform the same logic as the recursive solution: handle base cases, try all 9 move combinations, and compute the maximum.
- Before returning, store the computed result in the cache.
- Return the cached result.
class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
cache = {}
def dfs(r, c1, c2):
if (r, c1, c2) in cache:
return cache[(r, c1, c2)]
if c1 == c2 or min(c1, c2) < 0 or max(c1, c2) >= COLS:
return 0
if r == ROWS - 1:
return grid[r][c1] + grid[r][c2]
res = 0
for c1_d in [-1, 0, 1]:
for c2_d in [-1, 0, 1]:
res = max(res, dfs(r + 1, c1 + c1_d, c2 + c2_d))
cache[(r, c1, c2)] = res + grid[r][c1] + (grid[r][c2] if c1 != c2 else 0)
return cache[(r, c1, c2)]
return dfs(0, 0, COLS - 1)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns in the grid.
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of using recursion with memoization, we can build the solution iteratively from the bottom row up. For each cell combination at row r, we look at all possible transitions from row r+1 and take the maximum. This eliminates recursion overhead and makes the computation order explicit.
Algorithm
- Create a 3D DP array of size
[ROWS][COLS][COLS]. - Process rows from bottom to top (
r = ROWS-1to0). - For each
(c1, c2)pair at rowr:- Calculate cherries at current positions (handling overlap).
- If at the last row, the value is just the current cherries.
- Otherwise, look at all 9 possible
(nc1, nc2)positions in rowr+1. - Take the maximum from valid next positions and add current cherries.
- Store the result in
dp[r][c1][c2]. - Return
dp[0][0][COLS-1].
class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
dp = [[[0] * COLS for _ in range(COLS)] for _ in range(ROWS)]
for r in range(ROWS - 1, -1, -1):
for c1 in range(COLS):
for c2 in range(COLS):
res = grid[r][c1]
if c1 != c2:
res += grid[r][c2]
if r != ROWS - 1:
max_cherries = 0
for d1 in [-1, 0, 1]:
for d2 in [-1, 0, 1]:
nc1, nc2 = c1 + d1, c2 + d2
if 0 <= nc1 < COLS and 0 <= nc2 < COLS:
max_cherries = max(max_cherries, dp[r + 1][nc1][nc2])
res += max_cherries
dp[r][c1][c2] = res
return dp[0][0][COLS - 1]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns in the grid.
4. Dynamic Programming (Space Optimized)
Intuition
Since each row's computation only depends on the row below it, we do not need to store the entire 3D array. We can use two 2D arrays: one for the current row and one for the previous row (the row below). After processing each row, we swap them. This reduces space complexity from O(n * m^2) to O(m^2).
Algorithm
- Create a 2D
dparray of size[COLS][COLS]for the previous row's results. - Process rows from bottom to top.
- For each row, create a new 2D
cur_dparray for the current row. - For each
(c1, c2)pair wherec1 <= c2:- Calculate cherries at current positions.
- Look at all 9 next positions in the
dparray (previous row). - Take the maximum and add current cherries.
- After processing the row, set
dp = cur_dp. - Return
dp[0][COLS-1].
class Solution:
def cherryPickup(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
dp = [[0] * COLS for _ in range(COLS)]
for r in reversed(range(ROWS)):
cur_dp = [[0] * COLS for _ in range(COLS)]
for c1 in range(COLS):
for c2 in range(c1, COLS):
max_cherries = 0
cherries = grid[r][c1] + (grid[r][c2] if c1 != c2 else 0)
for d1 in [-1, 0, 1]:
for d2 in [-1, 0, 1]:
nc1, nc2 = c1 + d1, c2 + d2
if 0 <= nc1 < COLS and 0 <= nc2 < COLS:
max_cherries = max(max_cherries, cherries + dp[nc1][nc2])
cur_dp[c1][c2] = max_cherries
dp = cur_dp
return dp[0][COLS - 1]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns in the grid.
Common Pitfalls
Double Counting Cherries When Robots Overlap
Forgetting to check if both robots land on the same cell and counting cherries twice. When c1 == c2, you must only add grid[r][c1] once, not grid[r][c1] + grid[r][c2].
# Wrong: always adds both
res = grid[r][c1] + grid[r][c2]
# Correct: check for overlap
res = grid[r][c1] + (grid[r][c2] if c1 != c2 else 0)Using 4 Directions Instead of 3
Treating this like a general grid traversal where robots can move in any direction. Each robot can only move down-left, down, or down-right (3 choices), not up or stay in place. This gives 9 total combinations per step, not 16.
Incorrect State Space for Memoization
Using only (r, c1) or (r, c2) for caching, forgetting that the state depends on both robot positions simultaneously. The correct state is (r, c1, c2) since both robots move together row by row.
Off-by-One Error on Base Case
Checking r == ROWS instead of r == ROWS - 1 for the base case. Since indexing is 0-based, the last row is at index ROWS - 1, and that is where you should collect final cherries and stop recursion.