904. Fruit into Basket - Explanation
Description
You are visiting a farm that has a single row of fruit trees arranged from left to right. The trees are represented by an integer array fruits where fruits[i] is the type of fruit the i-th tree produces.
You want to collect as much fruit as possible. However, the owner has some strict rules that you must follow:
- You only have two baskets, and each basket can only hold a single type of fruit. There is no limit on the amount of fruit each basket can hold.
- Starting from any tree of your choice, you must pick exactly one fruit from every tree (including the start tree) while moving to the right. The picked fruits must fit in one of your baskets.
- Once you reach a tree with fruit that cannot fit in your baskets, you must stop.
Given the integer array fruits, return the maximum number of fruits you can pick.
Example 1:
Input: fruits = [1,2,1]
Output: 3Explanation: We can pick from all trees.
Example 2:
Input: fruits = [0,1,2,2]
Output: 3Explanation: We can pick from trees [1,2,2].
If we had started at the first tree, we would only pick from trees [0,1].
Example 3:
Input: fruits = [1,2,3,2,2]
Output: 4Explanation: We can pick from trees [2,3,2,2].
If we had started at the first tree, we would only pick from trees [1,2].
Constraints:
1 <= fruits.length <= 100,0000 <= fruits[i] < fruits.length
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Sliding Window - Maintaining a dynamic window that expands and contracts based on constraints
- Hash Map/Dictionary - Tracking counts of elements within the current window
- Two Pointers - Managing left and right boundaries of a subarray
- Window Invariant - Understanding how to restore validity when constraints are violated
1. Brute Force
Intuition
This problem asks for the longest contiguous subarray containing at most two distinct values. We can check every possible starting position and extend as far as possible while keeping track of at most two fruit types.
For each starting index, we greedily expand until we encounter a third distinct type, then record the length.
Algorithm
- For each starting index
i:- Initialize an empty set
typesto track distinct fruit types. - Extend
jfromiwhile we have fewer than two types or the current fruit is already in ourtypesset. - Add each fruit to the set and increment
j. - Record the length
j - i.
- Initialize an empty set
- Return the maximum length found.
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Sliding Window - I
Intuition
Instead of restarting from each position, we maintain a sliding window that always contains at most two fruit types. When we encounter a third type, we shrink the window from the left until only two types remain.
A hash map tracks the count of each fruit type in the current window. When a count drops to zero, we remove that type from the map.
Algorithm
- Initialize a hash map
count, left pointerl = 0, andres = 0. - For each right index
r:- Add
fruits[r]to thecountmap. - While the map has more than two keys:
- Decrement
count[fruits[l]]. - If it becomes zero, remove that key.
- Increment
l.
- Decrement
- Update
reswith the current window size.
- Add
- Return
res.
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
count = defaultdict(int)
l, total, res = 0, 0, 0
for r in range(len(fruits)):
count[fruits[r]] += 1
total += 1
while len(count) > 2:
f = fruits[l]
count[f] -= 1
total -= 1
l += 1
if not count[f]:
count.pop(f)
res = max(res, total)
return resTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
3. Sliding Window - II
Intuition
Similar to the advanced sliding window for frequency problems, we only care about the maximum window size. Once we achieve a valid window of size w, we never need a smaller one.
When a third fruit type appears, instead of shrinking until valid, we just slide the window forward by one position. The window size only increases when we find valid configurations, and the final size equals our answer.
Algorithm
- Initialize a hash map
countand left pointerl = 0. - For each right index
r:- Add
fruits[r]to thecountmap. - If the map has more than two keys:
- Decrement
count[fruits[l]]. - If it becomes zero, remove that key.
- Increment
l.
- Decrement
- Add
- Return
n - las the maximum window size.
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
count = defaultdict(int)
l = 0
for r in range(len(fruits)):
count[fruits[r]] += 1
if len(count) > 2:
count[fruits[l]] -= 1
if count[fruits[l]] == 0:
count.pop(fruits[l])
l += 1
return len(fruits) - lTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
4. Sliding Window - III
Intuition
Since we only track two fruit types, we can avoid using a hash map entirely. Instead, we store the two fruit types and the last index where each appeared. When a third type appears, we can immediately jump the left pointer to just after the earlier of the two last indices.
This approach uses O(1) space and handles the window adjustment in constant time by directly computing where the new window should start.
Algorithm
- Track
fruit1,fruit2, and their last indicesfruit1_lastIdx,fruit2_lastIdx. - Initialize
l = 0andres = 1. - For each index
r:- If
fruits[r]matchesfruit1, updatefruit1_lastIdx. - Else if it matches
fruit2(orfruit2is unset), update accordingly. - Else (third type found):
- The fruit with the smaller last index gets replaced.
- Update
lto be one past that smaller index. - Set the new fruit and its index.
- Update
reswithr - l + 1.
- If
- Return
res.
class Solution:
def totalFruit(self, fruits: list[int]) -> int:
l = 0
fruit1_lastIdx = 0
fruit2_lastIdx = -1
fruit1 = fruits[0]
fruit2 = -1
total = res = 1
for r in range(len(fruits)):
f = fruits[r]
if f == fruit1:
total += 1
fruit1_lastIdx = r
elif f == fruit2 or fruit2 == -1:
total += 1
fruit2_lastIdx = r
fruit2 = f
else:
if fruit2_lastIdx == min(fruit1_lastIdx, fruit2_lastIdx):
fruit1_lastIdx, fruit2_lastIdx = fruit2_lastIdx, fruit1_lastIdx
fruit1, fruit2 = fruit2, fruit1
total -= (fruit1_lastIdx - l + 1)
l = fruit1_lastIdx + 1
fruit1 = f
fruit1_lastIdx = r
res = max(res, r - l + 1)
return resTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Not Removing Zero-Count Entries from the Map
When shrinking the window and decrementing fruit counts, forgetting to remove entries with count zero from the hash map causes the map size to incorrectly indicate more than two fruit types. Always check if a count becomes zero and remove that key from the map before checking the size constraint.
Off-by-One Errors in Window Size Calculation
The window size is r - l + 1, not r - l. Forgetting the +1 leads to undercounting the maximum fruits by one. This is especially easy to miss in the advanced sliding window approach where the final answer is n - l, which correctly accounts for the inclusive nature of the window.
Confusing "At Most Two Types" with "Exactly Two Types"
The problem asks for at most two distinct fruit types, not exactly two. Some solutions incorrectly handle the case where all fruits are the same type (only one type present). The window should still be valid when containing just one fruit type, so the condition should check count.size() > 2, not count.size() != 2.
Sign in to join the discussion