3363. Find the Maximum Number of Fruits Collected
Problem Description
You have an n x n
grid representing a game dungeon where each room (i, j)
contains fruits[i][j]
fruits. Three children start at three corner positions:
- Child 1: starts at
(0, 0)
(top-left corner) - Child 2: starts at
(0, n-1)
(top-right corner) - Child 3: starts at
(n-1, 0)
(bottom-left corner)
All three children need to reach the room (n-1, n-1)
(bottom-right corner) in exactly n-1
moves.
Movement Rules:
- Child from
(0, 0)
can move from room(i, j)
to:(i+1, j+1)
,(i+1, j)
, or(i, j+1)
- Child from
(0, n-1)
can move from room(i, j)
to:(i+1, j-1)
,(i+1, j)
, or(i+1, j+1)
- Child from
(n-1, 0)
can move from room(i, j)
to:(i-1, j+1)
,(i, j+1)
, or(i+1, j+1)
Fruit Collection Rules:
- When a child enters a room, they collect all fruits in that room
- If multiple children visit the same room, only one child collects the fruits (the room becomes empty after the first child leaves)
The goal is to find the maximum total number of fruits that all three children can collect combined.
Key Insight: Due to the movement constraints and the requirement of exactly n-1
moves:
- Child 1 (starting from
(0, 0)
) can only reach(n-1, n-1)
by moving along the main diagonal - Child 2 (starting from
(0, n-1)
) can only move through rooms above the main diagonal - Child 3 (starting from
(n-1, 0)
) can only move through rooms below the main diagonal
This ensures that except for the destination (n-1, n-1)
, no room is visited by multiple children, simplifying the problem to finding optimal paths for each child in their respective regions.
Intuition
The key observation is understanding the movement constraints and what they mean for each child's possible paths.
First, let's think about Child 1 starting from (0, 0)
. To reach (n-1, n-1)
in exactly n-1
moves, we need to increase both row and column indices from 0 to n-1
. Since we have exactly n-1
moves and need to increase each coordinate by n-1
, the only way is to move diagonally at each step. This means Child 1 must follow the main diagonal path: (0,0) → (1,1) → (2,2) → ... → (n-1, n-1)
.
For Child 2 starting from (0, n-1)
, they need to move down (increase row from 0 to n-1
) while moving left (decrease column from n-1
to n-1
, so net change is 0). Looking at their movement options, they can move diagonally down-left, straight down, or diagonally down-right. Since they start at the top-right corner and must reach the bottom-right corner in exactly n-1
moves, they can only traverse rooms that are above or on the main diagonal.
Similarly, Child 3 starting from (n-1, 0)
needs to move right (increase column from 0 to n-1
) while their row stays at n-1
. Their movement options allow them to move up-right, right, or down-right. Given their starting position and destination, they can only traverse rooms that are below or on the main diagonal.
This spatial separation is crucial - it means the three children operate in mostly non-overlapping regions:
- Child 1: main diagonal only
- Child 2: upper triangle (above main diagonal)
- Child 3: lower triangle (below main diagonal)
The only potential overlap is at the destination (n-1, n-1)
, but since all children must reach there, we can handle this separately.
Since the regions don't overlap (except at the destination), we can solve each child's path independently using dynamic programming. For each child in their respective region, we want to find the path that collects maximum fruits.
For dynamic programming, we define f[i][j]
as the maximum fruits a child can collect when reaching position (i, j)
. The state transitions follow from the movement rules - at each position, we take the maximum from all valid previous positions plus the fruits at the current position.
The final answer combines:
- All fruits on the main diagonal (collected by Child 1)
- Maximum fruits Child 2 can collect reaching
(n-2, n-1)
(one step before destination) - Maximum fruits Child 3 can collect reaching
(n-1, n-2)
(one step before destination)
We use positions (n-2, n-1)
and (n-1, n-2)
because these are the last positions before the destination for Children 2 and 3 respectively, avoiding double-counting the fruits at (n-1, n-1)
.
Learn more about Dynamic Programming patterns.
Solution Approach
Based on our intuition, we implement the solution using dynamic programming with a single 2D array that we reuse for calculating paths for both Child 2 and Child 3.
Step 1: Initialize the DP array
We create a 2D array f
of size n x n
, initialized with negative infinity (-inf
) to represent unvisited states. This ensures we only consider valid paths.
Step 2: Calculate maximum fruits for Child 2 (starting from (0, n-1)
)
We set the starting position: f[0][n-1] = fruits[0][n-1]
Then we iterate through the upper triangle region. For each position (i, j)
where j > i
(above the main diagonal), we calculate:
f[i][j] = max(f[i-1][j], f[i-1][j-1]) + fruits[i][j]
if j + 1 < n:
f[i][j] = max(f[i][j], f[i-1][j+1] + fruits[i][j])
This implements the state transition: Child 2 can reach (i, j)
from three possible previous positions:
(i-1, j-1)
- diagonal down-left move(i-1, j)
- straight down move(i-1, j+1)
- diagonal down-right move (only valid ifj+1 < n
)
We take the maximum fruits from these previous positions and add the fruits at the current position.
Step 3: Calculate maximum fruits for Child 3 (starting from (n-1, 0)
)
We reuse the same f
array. First, set the starting position: f[n-1][0] = fruits[n-1][0]
Then iterate through the lower triangle region. For each position (i, j)
where i > j
(below the main diagonal), we calculate:
f[i][j] = max(f[i][j-1], f[i-1][j-1]) + fruits[i][j]
if i + 1 < n:
f[i][j] = max(f[i][j], f[i+1][j-1] + fruits[i][j])
This implements Child 3's possible moves to reach (i, j)
from:
(i-1, j-1)
- diagonal up-right move(i, j-1)
- straight right move(i+1, j-1)
- diagonal down-right move (only valid ifi+1 < n
)
Step 4: Calculate the final answer
The total maximum fruits collected is:
sum(fruits[i][i] for i in range(n)) + f[n-2][n-1] + f[n-1][n-2]
This combines:
sum(fruits[i][i] for i in range(n))
: All fruits on the main diagonal collected by Child 1f[n-2][n-1]
: Maximum fruits Child 2 can collect when reaching(n-2, n-1)
f[n-1][n-2]
: Maximum fruits Child 3 can collect when reaching(n-1, n-2)
The positions (n-2, n-1)
and (n-1, n-2)
are the last positions before the destination for Children 2 and 3 respectively. Since all three children reach (n-1, n-1)
, the fruits there are already counted in the diagonal sum for Child 1.
Time Complexity: O(n²)
as we iterate through the grid twice
Space Complexity: O(n²)
for the 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 with a 3×3 grid:
fruits = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Step 1: Identify each child's path constraints
-
Child 1 (starts at (0,0)): Must follow the main diagonal to reach (2,2) in exactly 2 moves
- Path: (0,0) → (1,1) → (2,2)
- Collects: 1 + 5 + 9 = 15
-
Child 2 (starts at (0,2)): Can only move through the upper triangle
- Must reach (2,2) from (0,2) in 2 moves
- Can visit positions above the diagonal
-
Child 3 (starts at (2,0)): Can only move through the lower triangle
- Must reach (2,2) from (2,0) in 2 moves
- Can visit positions below the diagonal
Step 2: Calculate optimal path for Child 2
Initialize DP array f
with -∞, then set starting position:
f[0][2] = 3
For position (1,2) (above diagonal since 2 > 1):
- Can come from: (0,1) or (0,2)
f[0][1]
is -∞ (not reachable)f[0][2] = 3
- So
f[1][2] = 3 + 6 = 9
Child 2's optimal path: (0,2) → (1,2) → (2,2)
Maximum fruits before destination: f[1][2] = 9
Step 3: Calculate optimal path for Child 3
Reset relevant parts of f
and set starting position:
f[2][0] = 7
For position (2,1) (below diagonal since 2 > 1):
- Can come from: (1,0) or (2,0)
f[1][0]
is -∞ (not reachable)f[2][0] = 7
- So
f[2][1] = 7 + 8 = 15
Child 3's optimal path: (2,0) → (2,1) → (2,2)
Maximum fruits before destination: f[2][1] = 15
Step 4: Calculate total fruits
Total = Diagonal sum + Child 2's fruits at (1,2) + Child 3's fruits at (2,1) Total = (1 + 5 + 9) + 9 + 15 = 15 + 9 + 15 = 39
Verification of non-overlapping paths:
- Child 1 visits: (0,0), (1,1), (2,2)
- Child 2 visits: (0,2), (1,2), (2,2)
- Child 3 visits: (2,0), (2,1), (2,2)
Only (2,2) is visited by multiple children, and we've counted its fruits only once (in Child 1's diagonal sum).
Solution Implementation
1class Solution:
2 def maxCollectedFruits(self, fruits: List[List[int]]) -> int:
3 n = len(fruits)
4
5 # Initialize DP table with negative infinity
6 # dp[i][j] represents maximum fruits collected at position (i, j)
7 dp = [[float('-inf')] * n for _ in range(n)]
8
9 # First path: from top-right corner moving downward-left
10 # Start from position (0, n-1)
11 dp[0][n - 1] = fruits[0][n - 1]
12
13 # Fill DP table for the first path
14 # Can move from (i-1, j), (i-1, j-1), or (i-1, j+1) to (i, j)
15 for i in range(1, n):
16 for j in range(i + 1, n): # Only consider valid positions where j > i
17 # Try moving from directly above
18 dp[i][j] = max(dp[i][j], dp[i - 1][j] + fruits[i][j])
19
20 # Try moving from upper-left diagonal
21 if j - 1 >= 0:
22 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + fruits[i][j])
23
24 # Try moving from upper-right diagonal
25 if j + 1 < n:
26 dp[i][j] = max(dp[i][j], dp[i - 1][j + 1] + fruits[i][j])
27
28 # Second path: from bottom-left corner moving rightward-up
29 # Reset starting position at (n-1, 0)
30 dp[n - 1][0] = fruits[n - 1][0]
31
32 # Fill DP table for the second path
33 # Can move from (i, j-1), (i-1, j-1), or (i+1, j-1) to (i, j)
34 for j in range(1, n):
35 for i in range(j + 1, n): # Only consider valid positions where i > j
36 # Try moving from left
37 dp[i][j] = max(dp[i][j], dp[i][j - 1] + fruits[i][j])
38
39 # Try moving from upper-left diagonal
40 if i - 1 >= 0:
41 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + fruits[i][j])
42
43 # Try moving from lower-left diagonal
44 if i + 1 < n:
45 dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + fruits[i][j])
46
47 # Calculate total fruits collected:
48 # Main diagonal path + first path ending at (n-2, n-1) + second path ending at (n-1, n-2)
49 diagonal_sum = sum(fruits[i][i] for i in range(n))
50 first_path_max = dp[n - 2][n - 1]
51 second_path_max = dp[n - 1][n - 2]
52
53 return diagonal_sum + first_path_max + second_path_max
54
1class Solution {
2 public int maxCollectedFruits(int[][] fruits) {
3 int n = fruits.length;
4 final int NEGATIVE_INFINITY = 1 << 29; // Large negative value to represent impossible states
5
6 // dp[i][j] represents the maximum fruits collected at position (i, j)
7 int[][] dp = new int[n][n];
8
9 // Initialize all cells with negative infinity (impossible to reach initially)
10 for (int[] row : dp) {
11 Arrays.fill(row, -NEGATIVE_INFINITY);
12 }
13
14 // Phase 1: Calculate maximum fruits for the upper-right triangle
15 // Starting from top-right corner (0, n-1)
16 dp[0][n - 1] = fruits[0][n - 1];
17
18 // Fill the upper-right triangle row by row
19 for (int row = 1; row < n; row++) {
20 for (int col = row + 1; col < n; col++) {
21 // Can come from three positions: up, up-left, or up-right
22 // From directly above (row-1, col)
23 dp[row][col] = Math.max(dp[row - 1][col], dp[row - 1][col - 1]) + fruits[row][col];
24
25 // Check if we can come from up-right (row-1, col+1)
26 if (col + 1 < n) {
27 dp[row][col] = Math.max(dp[row][col], dp[row - 1][col + 1] + fruits[row][col]);
28 }
29 }
30 }
31
32 // Phase 2: Calculate maximum fruits for the bottom-left triangle
33 // Starting from bottom-left corner (n-1, 0)
34 dp[n - 1][0] = fruits[n - 1][0];
35
36 // Fill the bottom-left triangle column by column
37 for (int col = 1; col < n; col++) {
38 for (int row = col + 1; row < n; row++) {
39 // Can come from three positions: left, up-left, or down-left
40 // From left (row, col-1) or diagonal up-left (row-1, col-1)
41 dp[row][col] = Math.max(dp[row][col - 1], dp[row - 1][col - 1]) + fruits[row][col];
42
43 // Check if we can come from down-left (row+1, col-1)
44 if (row + 1 < n) {
45 dp[row][col] = Math.max(dp[row][col], dp[row + 1][col - 1] + fruits[row][col]);
46 }
47 }
48 }
49
50 // Calculate the final answer
51 // Sum of fruits from two paths ending at positions adjacent to bottom-right corner
52 int totalFruits = dp[n - 2][n - 1] + dp[n - 1][n - 2];
53
54 // Add all fruits on the main diagonal (collected by the main path)
55 for (int i = 0; i < n; i++) {
56 totalFruits += fruits[i][i];
57 }
58
59 return totalFruits;
60 }
61}
62
1class Solution {
2public:
3 int maxCollectedFruits(vector<vector<int>>& fruits) {
4 int n = fruits.size();
5 const int INF = 1 << 29; // Large negative value to represent impossible states
6
7 // dp[i][j] represents the maximum fruits collected at position (i, j)
8 vector<vector<int>> dp(n, vector<int>(n, -INF));
9
10 // First pass: Calculate maximum fruits for path from top-right corner
11 // Moving downward and possibly left/right within upper triangular region
12 dp[0][n - 1] = fruits[0][n - 1]; // Starting position at top-right
13
14 for (int row = 1; row < n; row++) {
15 for (int col = row + 1; col < n; col++) {
16 // Can arrive at (row, col) from three possible previous positions:
17 // 1. From (row-1, col) - moving straight down
18 // 2. From (row-1, col-1) - moving down-left
19 // 3. From (row-1, col+1) - moving down-right (if valid)
20
21 dp[row][col] = max(dp[row - 1][col], dp[row - 1][col - 1]) + fruits[row][col];
22
23 if (col + 1 < n) {
24 dp[row][col] = max(dp[row][col], dp[row - 1][col + 1] + fruits[row][col]);
25 }
26 }
27 }
28
29 // Second pass: Calculate maximum fruits for path from bottom-left corner
30 // Moving rightward and possibly up/down within lower triangular region
31 dp[n - 1][0] = fruits[n - 1][0]; // Starting position at bottom-left
32
33 for (int col = 1; col < n; col++) {
34 for (int row = col + 1; row < n; row++) {
35 // Can arrive at (row, col) from three possible previous positions:
36 // 1. From (row, col-1) - moving straight right
37 // 2. From (row-1, col-1) - moving right-up
38 // 3. From (row+1, col-1) - moving right-down (if valid)
39
40 dp[row][col] = max(dp[row][col - 1], dp[row - 1][col - 1]) + fruits[row][col];
41
42 if (row + 1 < n) {
43 dp[row][col] = max(dp[row][col], dp[row + 1][col - 1] + fruits[row][col]);
44 }
45 }
46 }
47
48 // Calculate total fruits collected:
49 // 1. Fruits from top-right path ending at (n-2, n-1)
50 // 2. Fruits from bottom-left path ending at (n-1, n-2)
51 // 3. All fruits on the main diagonal (collected by the fixed diagonal path)
52 int totalFruits = dp[n - 2][n - 1] + dp[n - 1][n - 2];
53
54 for (int i = 0; i < n; i++) {
55 totalFruits += fruits[i][i]; // Add diagonal elements
56 }
57
58 return totalFruits;
59 }
60};
61
1/**
2 * Calculates the maximum fruits that can be collected from a grid
3 * @param fruits - 2D array representing the fruit values in the grid
4 * @returns The maximum number of fruits that can be collected
5 */
6function maxCollectedFruits(fruits: number[][]): number {
7 const gridSize: number = fruits.length;
8 const NEGATIVE_INFINITY: number = 1 << 29;
9
10 // Initialize DP table with negative infinity values
11 const dpTable: number[][] = Array.from(
12 { length: gridSize },
13 () => Array(gridSize).fill(-NEGATIVE_INFINITY)
14 );
15
16 // Process path from top-right corner
17 // Initialize starting position for first path
18 dpTable[0][gridSize - 1] = fruits[0][gridSize - 1];
19
20 // Fill DP table for the upper-right triangle
21 for (let row = 1; row < gridSize; row++) {
22 for (let col = row + 1; col < gridSize; col++) {
23 // Calculate maximum from three possible previous positions
24 dpTable[row][col] = Math.max(
25 dpTable[row - 1][col],
26 dpTable[row - 1][col - 1]
27 ) + fruits[row][col];
28
29 // Check if we can come from the right diagonal
30 if (col + 1 < gridSize) {
31 dpTable[row][col] = Math.max(
32 dpTable[row][col],
33 dpTable[row - 1][col + 1] + fruits[row][col]
34 );
35 }
36 }
37 }
38
39 // Process path from bottom-left corner
40 // Initialize starting position for second path
41 dpTable[gridSize - 1][0] = fruits[gridSize - 1][0];
42
43 // Fill DP table for the lower-left triangle
44 for (let col = 1; col < gridSize; col++) {
45 for (let row = col + 1; row < gridSize; row++) {
46 // Calculate maximum from three possible previous positions
47 dpTable[row][col] = Math.max(
48 dpTable[row][col - 1],
49 dpTable[row - 1][col - 1]
50 ) + fruits[row][col];
51
52 // Check if we can come from the bottom diagonal
53 if (row + 1 < gridSize) {
54 dpTable[row][col] = Math.max(
55 dpTable[row][col],
56 dpTable[row + 1][col - 1] + fruits[row][col]
57 );
58 }
59 }
60 }
61
62 // Calculate the final answer
63 // Sum of the two paths ending at their respective destinations
64 let totalFruits: number = dpTable[gridSize - 2][gridSize - 1] + dpTable[gridSize - 1][gridSize - 2];
65
66 // Add all fruits from the main diagonal (the third path)
67 for (let i = 0; i < gridSize; i++) {
68 totalFruits += fruits[i][i];
69 }
70
71 return totalFruits;
72}
73
Time and Space Complexity
The time complexity is O(n²)
and the space complexity is O(n²)
, where n
is the side length of the grid.
Time Complexity Analysis:
- The code initializes a 2D array
f
with dimensionsn × n
, which takesO(n²)
time. - The first nested loop iterates through positions where
i
ranges from1
ton-1
and for eachi
,j
ranges fromi+1
ton-1
. This results in approximatelyn²/2
iterations, which isO(n²)
. - The second nested loop similarly iterates through positions where
j
ranges from1
ton-1
and for eachj
,i
ranges fromj+1
ton-1
. This also results in approximatelyn²/2
iterations, which isO(n²)
. - The final return statement computes the sum of diagonal elements, which takes
O(n)
time. - Overall:
O(n²) + O(n²) + O(n²) + O(n) = O(n²)
Space Complexity Analysis:
- The code creates a 2D array
f
of sizen × n
, which requiresO(n²)
space. - No other significant auxiliary space is used besides a few variables.
- Therefore, the space complexity is
O(n²)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect DP Table Reuse Between Paths
The Problem: When reusing the same DP table for both Child 2 and Child 3, failing to properly reset or isolate the calculations can lead to incorrect results. The code initializes dp[0][n-1]
for Child 2, then later sets dp[n-1][0]
for Child 3, but the previous calculations for Child 2 remain in the table and could interfere with Child 3's path calculation.
Why It Happens: The DP values from Child 2's calculation (in the upper triangle) might accidentally be accessed when computing Child 3's path if boundary conditions aren't carefully checked.
Solution: Ensure strict boundary checking so that each child's calculation only accesses valid positions:
- For Child 2: Only compute positions where
j > i
(upper triangle) - For Child 3: Only compute positions where
i > j
(lower triangle) - Add explicit checks before accessing any DP values
Pitfall 2: Off-by-One Errors in Movement Validation
The Problem: The movement rules have different constraints for each child. For example, when Child 2 at position (i, j)
tries to move from (i-1, j+1)
, we need to check if j+1 < n
. However, it's easy to forget these boundary checks or apply them incorrectly.
Why It Happens: Each child has three possible previous positions, and some of these moves require boundary validation while others don't, making it easy to miss or misplace checks.
Solution: Always validate array bounds before accessing:
# For Child 2 moving from (i-1, j+1)
if j + 1 < n and i - 1 >= 0: # Check both dimensions
dp[i][j] = max(dp[i][j], dp[i - 1][j + 1] + fruits[i][j])
# For Child 3 moving from (i+1, j-1)
if i + 1 < n and j - 1 >= 0: # Check both dimensions
dp[i][j] = max(dp[i][j], dp[i + 1][j - 1] + fruits[i][j])
Pitfall 3: Incorrect Loop Order for Child 3
The Problem: The loop order matters significantly for dynamic programming. For Child 3 starting at (n-1, 0)
and moving towards (n-1, n-1)
, using the wrong loop order can cause attempts to access DP values that haven't been computed yet.
Why It Happens: Child 3 moves rightward and potentially up/down. The outer loop should iterate through columns (j) and the inner loop through rows (i) to ensure we compute positions in the correct dependency order.
Solution: Use the correct loop structure:
# Correct order for Child 3
for j in range(1, n): # Column first (moving right)
for i in range(j + 1, n): # Then rows in valid range
# Compute dp[i][j] based on positions to the left
Pitfall 4: Missing Initialization After Reusing DP Table
The Problem: When reusing the DP table between children, forgetting to handle the initialization properly can cause incorrect propagation of values. The code sets dp[n-1][0] = fruits[n-1][0]
for Child 3, but if this overwrites a value needed from Child 2's calculation, it could affect the final result.
Why It Happens: The regions for Child 2 (upper triangle) and Child 3 (lower triangle) are separate except at the boundaries, but the initialization points and final collection points need careful handling.
Solution: Ensure that:
- Child 2's final position
(n-2, n-1)
is computed and stored before starting Child 3's calculation - Child 3's starting position
(n-1, 0)
doesn't interfere with Child 2's results - Consider storing intermediate results if needed:
# Store Child 2's result before processing Child 3 child2_result = dp[n - 2][n - 1] # Now safe to reinitialize for Child 3 dp[n - 1][0] = fruits[n - 1][0]
How many ways can you arrange the three letters A, B and C?
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!