2106. Maximum Fruits Harvested After at Most K Steps
Problem Description
In this problem, we are given a sorted 2D integer array fruits
where each entry fruits[i]
consists of two integers, position_i
and amount_i
. position_i
represents a unique position on an infinite x-axis where fruits are located, and amount_i
is the number of fruits available at that position. Additionally, we have a starting position on the axis, startPos
, and an integer k
which represents the maximum number of steps we can take in total. The goal is to find the maximum total number of fruits one can harvest.
One can walk either left or right on the x-axis, and upon visiting a position, all fruits there are harvested and thus removed from that position. Walking one unit on the x-axis counts as one step, and the total number of steps cannot exceed k
. The task is to devise a strategy for harvesting the most fruits under these constraints.
Intuition
To arrive at the solution, we first need to understand that walking too far in one direction might prevent collecting fruits in the other direction because of the step limit k
. To maximize the harvest, we should consider walking in one direction to collect fruits and then potentially changing direction to collect more if the steps allow.
The intuition behind the solution is to use a sliding window technique to keep track of the total amount of fruits that can be collected within k
steps from startPos
. We slide the window across the sorted positions while ensuring the total number of steps including the return to startPos
does not exceed k
. If at any point the total distance of our sliding window exceeds k
, we move the starting point of our sliding window to the right to shrink the harvesting range back within the allowed steps.
We calculate the maximum number of fruits that can be collected within this range and update our answer accordingly. At each step, we consider reaching the farthest point in our window from startPos
and then returning to the nearest point. The heart of this approach relies on the optimal substructure of the problem—maximizing fruits within a range naturally contributes to maximizing the overall harvest.
Learn more about Binary Search, Prefix Sum and Sliding Window patterns.
Solution Approach
The solution implements a sliding window approach to maintain a range of positions we can visit to collect fruits within k
steps. The fruits
array gives us the positions and the number of fruits at each position, sorted by the positions. The implementation iterates over this array, expanding and shrinking the window to find the optimal total amount of fruits that can be harvested.
The maxTotalFruits
function starts by initializing important variables: ans
to store the maximum number of fruits we can harvest, i
to denote the start of the sliding window, s
to keep the sum of fruits within the window, and j
to iterate over the fruit positions.
Here is a step-by-step explanation of the algorithm:
- Iterate through the fruit array using the index
j
and for each positionpj
with fruit countfj
, addfj
to the sums
. - Check if the current window
[i, j]
exceedsk
steps. This is done by calculating the distancepj - fruits[i][0]
(the width of the window) and adding the smaller distance fromstartPos
to either end of the window. - If the window is too wide (exceeds
k
steps), we shrink the window from the left by incrementingi
, effectively removing fruits atfruits[i][1]
from the sums
. - At each iteration, after adjusting the window, update
ans
with the maximum of itself or the current sums
. - Continue this process until all positions have been checked.
The core concept here leverages the fact that, since the fruit positions are sorted and unique, we can efficiently determine the range of positions to harvest by only considering the endpoints of the current window.
The algorithm uses constant space for variables and O(n)
time where n
is the number of positions since it scans through the positions once.
Here is a portion of the code explaining the critical part of the algorithm:
for j, (pj, fj) in enumerate(fruits):
s += fj
while i <= j and pj - fruits[i][0] + min(abs(startPos - fruits[i][0]), abs(startPos - pj)) > k:
s -= fruits[i][1]
i += 1
ans = max(ans, s)
The loop adds fruits to s
and potentially contracts the window by advancing i
if the condition (pj - fruits[i][0] + min(abs(startPos - fruits[i][0]), abs(startPos - pj))) > k
is met, ensuring the total steps do not exceed k
. The answer ans
is updated to the maximum sum s
found within the constraints.
This elegant approach effectively balances expanding and contracting the harvesting range on the fly to maximize fruit collection within the step limit.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example to illustrate the solution approach. Suppose we have the following input:
fruits
array:[[0,3], [2,1], [5,2]]
startPos
: 1k
: 4
Here, we can move a maximum of 4 steps from the starting position, and positions [0, 2, 5]
have [3, 1, 2]
fruits respectively. We want to maximize the number of fruits we can pick within those 4 steps.
Here's how the solution approach would work on this example:
- Initialize
ans
as 0 (maximum number of fruits harvested),i
as 0 (start of the sliding window), ands
as 0 (sum of fruits within the window). - Start iterating through
fruits
array with indexj
. As we addfj
to sums
, the sum of fruits becomes 3 whenj = 0
. - There is no need to shrink the window yet since we are within
k
steps range (the max steps we can move is4
). - Move to the next item in the array,
j = 1
. The sum of fruitss
becomes3 + 1 = 4
. The window[i, j]
is now[0, 2]
, and the total steps required areabs(startPos - fruits[i][0]) + (fruits[j][0] - fruits[i][0]) = abs(1 - 0) + (2 - 0) = 3
steps to go fromstartPos
topj
, which still does not exceedk
. - Move to the next item in the array,
j = 2
. Add fruit count at that position tos
, so nows = 4 + 2 = 6
. The window[i, j]
is now[0, 5]
, and we check the steps:abs(startPos - fruits[i][0]) + (fruits[j][0] - fruits[i][0]) = abs(1 - 0) + (5 - 0) = 6
. This exceedsk
, so we need to shrink the window from the left by incrementingi
. - The new window
[i, j]
is now[2, 5]
. We updates
by subtracting the fruits atfruits[i][1]
and calculate steps again:s = 6 - fruits[i][1] = 6 - 3 = 3
. The steps areabs(startPos - fruits[i][0]) + (fruits[j][0] - fruits[i][0]) = abs(1 - 2) + (5 - 2) = 4
, which is equal tok
so we keep the window. - As
i
moves up,s
decreases to 2, having removed the fruits at position0
. Since this is still less thank
, we continue. - Now,
ans
is updated to a maximum of itself or current sums
, soans = max(0, 3) = 3
. - No more positions left, we end with
ans = 3
, which is the amount of fruit we harvested.
Therefore, following this approach with our example, the maximum number of fruits that can be harvested is 3
.
Solution Implementation
1class Solution:
2 def maxTotalFruits(self, fruits: List[List[int]], startPos: int, k: int) -> int:
3 # Initialize variables for the answer, starting index of the window, and the sum of fruits within the window
4 max_fruits = window_start = total_fruits = 0
5
6 # Iterate over the fruits list with index 'j' and unpack positions and fruits as 'position_j' and 'fruits_at_j'
7 for window_end, (position_j, fruits_at_j) in enumerate(fruits):
8 # Add the fruits at position 'j' to the running total within the window
9 total_fruits += fruits_at_j
10
11 # Shrink the window from the left as long as the condition is met
12 while (
13 window_start <= window_end
14 and position_j
15 - fruits[window_start][0] # Distance between the current end of the window and its start
16 + min(abs(startPos - fruits[window_start][0]), abs(startPos - position_j)) # Minimum distance to startPos from either end of the window
17 > k # Check if the total distance is within 'k'
18 ):
19 # If the window exceeds 'k' distance, subtract the fruits at 'window_start' and move window start to the right
20 total_fruits -= fruits[window_start][1]
21 window_start += 1
22
23 # Update the 'max_fruits' if the current window's total fruits is greater than the previously recorded maximum
24 max_fruits = max(max_fruits, total_fruits)
25
26 # Return the maximum number of fruits that can be collected within 'k' units of movement
27 return max_fruits
28
1class Solution {
2 public int maxTotalFruits(int[][] fruits, int startPos, int k) {
3 int maxFruits = 0; // Stores the maximum number of fruits we can collect
4 int currentSum = 0; // Stores the current sum of fruits between two points
5
6 // Two pointers approach: i is for the start point and j is for the end point
7 for (int i = 0, j = 0; j < fruits.length; ++j) {
8 int positionJ = fruits[j][0]; // The position of the j-th tree
9 int fruitsAtJ = fruits[j][1]; // The number of fruits at the j-th tree
10 currentSum += fruitsAtJ; // Add fruits at the j-th tree to the current sum
11
12 // Adjust the starting point i to not exceed the maximum distance k
13 while (i <= j &&
14 positionJ - fruits[i][0] +
15 Math.min(Math.abs(startPos - fruits[i][0]), Math.abs(startPos - positionJ))
16 > k) {
17 // Subtract the number of fruits at the i-th tree as we move the start point forward
18 currentSum -= fruits[i][1];
19 i++; // Increment the start point
20 }
21
22 // Update maxFruits with the maximum of current sum and previously calculated maxFruits
23 maxFruits = Math.max(maxFruits, currentSum);
24 }
25 return maxFruits; // Return the maximum number of fruits that can be collected
26 }
27}
28
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6 // Function to calculate the maximum total number of fruits that can be collected
7 // "fruits" vector holds pairs of positions and fruit counts, "startPos" is the starting position,
8 // "k" is the maximum number of steps that can be taken.
9 int maxTotalFruits(vector<vector<int>>& fruits, int startPos, int k) {
10 int maxFruits = 0; // Store the maximum total fruits that can be collected.
11 int currentSum = 0; // Store the current sum of fruits being considered.
12
13 // Use a sliding window defined by the indices [i, j].
14 for (int i = 0, j = 0; j < fruits.size(); ++j) {
15 int positionJ = fruits[j][0]; // Position of the j-th fruit tree
16 int fruitCountJ = fruits[j][1]; // Number of fruits at the j-th fruit tree
17 currentSum += fruitCountJ; // Add the fruits from the j-th tree to the current sum
18
19 // If the distance from start to the current fruit is more than k
20 // after adjusting for the closest path, shrink the window from the left.
21 while (i <= j && positionJ - fruits[i][0] +
22 min(abs(startPos - fruits[i][0]), abs(startPos - positionJ)) > k) {
23 currentSum -= fruits[i][1]; // Remove the fruits from the i-th tree from the current sum
24 i++; // Move the start of the window to the right
25 }
26
27 // Update maxFruits with the maximum of the current value and currentSum.
28 maxFruits = max(maxFruits, currentSum);
29 }
30
31 return maxFruits; // Return the maximum number of fruits that can be collected
32 }
33};
34
1/**
2 * Calculates the maximum total number of fruits that can be collected from fruit trees lined up in a row.
3 * The farmer starts at a given position and can move a maximum total distance k.
4 *
5 * @param {number[][]} fruitPositions - An array where each element is a pair [position, fruitCount];
6 * position is the position of a tree, fruitCount is the number of
7 * fruits at that tree.
8 * @param {number} startPos - The starting position of the farmer.
9 * @param {number} k - The maximum total distance the farmer can move.
10 * @returns {number} The maximum total fruits that can be collected.
11 */
12function maxTotalFruits(fruitPositions: number[][], startPos: number, k: number): number {
13 let maxFruits = 0; // The current maximum number of fruits that can be collected
14 let currentFruits = 0; // The running total of fruits collected within the current window
15
16 // Two-pointer approach to find the optimal range of trees to collect fruits from
17 for (let leftIndex = 0, rightIndex = 0; rightIndex < fruitPositions.length; rightIndex++) {
18 // Current fruit position and count
19 const [currentPosition, fruitCount] = fruitPositions[rightIndex];
20 currentFruits += fruitCount; // Add the fruits from the right pointer's current tree
21
22 // Adjust the left pointer while the total distance required to collect fruits
23 // from the range exceeds the maximum distance k
24 while (
25 leftIndex <= rightIndex &&
26 currentPosition - fruitPositions[leftIndex][0] +
27 Math.min(Math.abs(startPos - fruitPositions[leftIndex][0]), Math.abs(startPos - currentPosition)) > k
28 ) {
29 // Subtract the fruits from the left pointer's current tree and move the left pointer to the right
30 currentFruits -= fruitPositions[leftIndex++][1];
31 }
32
33 // Update the maximum fruits collected if the current total is greater
34 maxFruits = Math.max(maxFruits, currentFruits);
35 }
36
37 return maxFruits; // Return the maximum fruits collected after checking all ranges
38}
39
Time and Space Complexity
Time Complexity
The time complexity of the given code is O(N)
, where N
represents the number of pairs in the fruits
list. Although the code features a nested loop, each fruit in the fruits
list is processed only once due to the use of two-pointer technique. The inner while
loop only advances the i
pointer and does not perform excessive iterations for each j
index in the outer loop. Therefore, every element is visited at most twice, keeping the overall time complexity linear with respect to the number of fruit positions.
Space Complexity
The space complexity of the given code is O(1)
. The only extra space used is for variables to keep track of the current maximum fruit total (ans
), the current sum of fruits (s
), and the pointers (i
, j
) used for traversing the fruits
list. This use of space does not scale with the size of the input, and as such, remains constant.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
https algomonster s3 us east 2 amazonaws com cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in
Want a Structured Path to Master System Design Too? Don’t Miss This!