A clear explanation of Fruit Into Baskets using a sliding window with at most two distinct fruit types.
Problem Restatement
We are given an integer array fruits.
Each value represents the fruit type of one tree in a row.
We have two baskets. Each basket can hold only one fruit type, but it can hold unlimited fruits of that type.
Starting from any tree, we must move right and pick exactly one fruit from every tree. We cannot skip trees.
We stop when we reach a tree whose fruit type does not fit in either basket.
Return the maximum number of fruits we can pick.
This is the same as asking:
Find the length of the longest contiguous subarray with at most two distinct values.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer array fruits |
| Output | Maximum number of fruits we can pick |
| Rule | Pick from consecutive trees only |
| Basket limit | At most two fruit types |
| Constraint | 1 <= fruits.length <= 10^5 |
Function shape:
def totalFruit(fruits: list[int]) -> int:
...Examples
Example 1:
fruits = [1, 2, 1]We can pick all trees because there are only two fruit types.
Answer:
3Example 2:
fruits = [0, 1, 2, 2]The best valid segment is:
[1, 2, 2]It has two fruit types and length 3.
Answer:
3Example 3:
fruits = [1, 2, 3, 2, 2]The best valid segment is:
[2, 3, 2, 2]It has fruit types 2 and 3.
Answer:
4First Thought: Check Every Starting Tree
A direct method is to try every possible starting index.
From each start, move right while the segment contains at most two fruit types.
class Solution:
def totalFruit(self, fruits: list[int]) -> int:
n = len(fruits)
best = 0
for start in range(n):
types = set()
for end in range(start, n):
types.add(fruits[end])
if len(types) > 2:
break
best = max(best, end - start + 1)
return bestThis works, but it repeats too much work.
For each starting index, we scan forward again. In the worst case, this can take O(n^2) time.
Key Insight
We need the longest contiguous window that contains at most two distinct fruit types.
This is a standard sliding window condition.
We keep a window:
fruits[left:right + 1]The window is valid when it contains at most two distinct fruit types.
As we move right from left to right, we add one fruit to the window.
If the window has more than two fruit types, we move left rightward until the window becomes valid again.
During the scan, every valid window is a possible answer.
Algorithm
Use a hash map called count.
It stores how many times each fruit type appears in the current window.
Steps:
- Set
left = 0. - Set
answer = 0. - Iterate
rightfrom0tolen(fruits) - 1. - Add
fruits[right]tocount. - While
counthas more than two fruit types:- Remove
fruits[left]from the window. - If its count becomes
0, delete that fruit type. - Move
leftone step right.
- Remove
- Update the answer with the current window length.
Correctness
The algorithm keeps the window valid after each iteration.
When a new fruit is added, the window may contain three fruit types. If so, moving left rightward removes fruits from the left side until only two types remain.
After this shrinking step, the window ending at right is the longest valid window ending at right. If we moved left any less, the window would still have more than two fruit types. If we moved it more, the window would become shorter without need.
The algorithm checks the best valid window ending at every possible right index.
Since every valid contiguous picking sequence has some ending index, it will be considered when that ending index is processed.
Therefore, the maximum length recorded by the algorithm is exactly the maximum number of fruits we can pick.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each index enters and leaves the window at most once |
| Space | O(1) | The map stores at most three fruit types before shrinking |
Although the hash map is used, the basket limit is fixed at two types, so the space is constant.
Implementation
from collections import defaultdict
class Solution:
def totalFruit(self, fruits: list[int]) -> int:
count = defaultdict(int)
left = 0
answer = 0
for right, fruit in enumerate(fruits):
count[fruit] += 1
while len(count) > 2:
left_fruit = fruits[left]
count[left_fruit] -= 1
if count[left_fruit] == 0:
del count[left_fruit]
left += 1
answer = max(answer, right - left + 1)
return answerCode Explanation
The hash map stores fruit counts inside the current window:
count = defaultdict(int)The left boundary starts at the first tree:
left = 0We expand the window by moving right:
for right, fruit in enumerate(fruits):
count[fruit] += 1If there are more than two fruit types, the window breaks the basket rule:
while len(count) > 2:So we remove fruits from the left:
left_fruit = fruits[left]
count[left_fruit] -= 1If a fruit type no longer appears in the window, remove it from the map:
if count[left_fruit] == 0:
del count[left_fruit]Then move the left boundary:
left += 1After the window is valid again, update the best answer:
answer = max(answer, right - left + 1)Testing
def run_tests():
s = Solution()
assert s.totalFruit([1, 2, 1]) == 3
assert s.totalFruit([0, 1, 2, 2]) == 3
assert s.totalFruit([1, 2, 3, 2, 2]) == 4
assert s.totalFruit([3, 3, 3, 1, 2, 1, 1, 2, 3, 3, 4]) == 5
assert s.totalFruit([1]) == 1
assert s.totalFruit([1, 1, 1, 1]) == 4
assert s.totalFruit([1, 2, 3, 4, 5]) == 2
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1, 2, 1] | Entire array is valid |
[0, 1, 2, 2] | Window must drop the first type |
[1, 2, 3, 2, 2] | Main mixed example |
| Long mixed input | Checks repeated shrinking |
| Single element | Minimum input |
| All same type | One basket is enough |
| All different | Best window has length 2 |