Topics › Sliding Window and Two Pointers › Medium Problems
Fruit Into Baskets
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
A row of trees is described by an array fruits, where fruits[i] is the type of fruit tree i produces. You have two baskets, and each basket can hold any amount of fruit but only of one type. You pick a starting tree and then move right, picking exactly one fruit from every tree, until you reach a tree whose fruit fits in neither basket. Return the maximum number of fruits you can collect.
Strip away the story and the task is: find the longest contiguous subarray that contains at most two distinct values. The starting tree is the left end of the subarray, and you stop just before the first tree that would introduce a third type.
Constraints: 1 ≤ n ≤ 10^5, 0 ≤ fruits[i] < n.
Example 1
fruits = [1, 2, 3, 2, 2]
answer = 4 // start at index 1: types 2 and 3 cover [2, 3, 2, 2]
Example 2
fruits = [3, 3, 3, 1, 2, 1, 1, 2, 3, 3, 4]
answer = 5 // [1, 2, 1, 1, 2] at indices 3..7 uses only types 1 and 2
Approach 1: Brute force
Intuition. Try every starting tree. From that start, walk right and add each type to a set. As soon as the set reaches three types, the walk from this start is over: every longer subarray from the same start also has three types. The longest walk over all starts is the answer.
Algorithm.
- For each start
i, create an empty settypes. - For
j = i ..: addfruits[j]. If the set size exceeds2, break. Otherwise updatebestwithj - i + 1. - Return
best.
static int bruteForce(int[] fruits) {
int n = fruits.length, best = 0;
for (int i = 0; i < n; i++) {
Set<Integer> types = new HashSet<>();
for (int j = i; j < n; j++) {
types.add(fruits[j]);
if (types.size() > 2) break; // a third type: no longer window from i can work
best = Math.max(best, j - i + 1);
}
}
return best;
}
Complexity. Time O(n^2): an array with only two types never triggers the break. Space O(1): the set never holds more than three values.
Approach 2: Better, shrinking sliding window
Intuition. “At most two distinct types” is a monotonic property. If a window satisfies it, so does every window inside it; if a window violates it, so does every window containing it. So we can keep a single window [l, r] and only ever move both edges to the right.
A set is no longer enough, because when we drop fruits[l] from the left we need to know whether another copy of that type is still inside the window. So we keep a count per type in a map. A type leaves the map only when its count reaches zero, which means count.size() is exactly the number of distinct types in the window.
Algorithm.
l = 0, empty mapcount,best = 0.- For each
r: incrementcount[fruits[r]]. - While the map has more than two keys: decrement
count[fruits[l]], remove the key if it hits zero, and incrementl. - Update
bestwithr - l + 1.
static int better(int[] fruits) {
Map<Integer, Integer> count = new HashMap<>();
int l = 0, best = 0;
for (int r = 0; r < fruits.length; r++) {
count.merge(fruits[r], 1, Integer::sum);
while (count.size() > 2) {
int f = fruits[l];
if (count.merge(f, -1, Integer::sum) == 0) count.remove(f);
l++;
}
best = Math.max(best, r - l + 1);
}
return best;
}
Map.merge(key, delta, Integer::sum) adds delta to the stored count (or stores delta if the key is absent) and returns the new count, which lets us test for zero in the same line.
Complexity. Time O(2n): every index enters the window once and leaves it at most once, and the map operations are O(1) because the map never holds more than three keys. Space O(1) for the same reason.
Approach 3: Optimal, the window that never shrinks
Intuition. We only want the maximum length. Suppose the best valid window seen so far has length L. Shrinking the window below L cannot help us find something longer. So when the third type arrives, we do not shrink until the window is valid; we drop exactly one fruit from the left, so the window slides right while keeping its length. While it is invalid we simply do not record it. If some later valid window is longer, the right edge will reach it with the window back at two types and the length goes up by one.
The effect is that the inner while becomes an if: at most one left-step per right-step.
Algorithm.
- Same setup as Approach 2.
- For each
r: incrementcount[fruits[r]]. - If the map has more than two keys: remove one occurrence of
fruits[l]and incrementl. - If the map now has at most two keys, update
bestwithr - l + 1.
static int optimal(int[] fruits) {
Map<Integer, Integer> count = new HashMap<>();
int l = 0, best = 0;
for (int r = 0; r < fruits.length; r++) {
count.merge(fruits[r], 1, Integer::sum);
if (count.size() > 2) { // slide: drop exactly one fruit from the left
int f = fruits[l];
if (count.merge(f, -1, Integer::sum) == 0) count.remove(f);
l++;
}
if (count.size() <= 2) best = Math.max(best, r - l + 1);
}
return best;
}
Dry run on fruits = [1, 2, 3, 2, 2]:
| r | add | count after add | slide? | l | valid? | best |
|---|---|---|---|---|---|---|
| 0 | 1 | {1:1} | no | 0 | yes | 1 |
| 1 | 2 | {1:1, 2:1} | no | 0 | yes | 2 |
| 2 | 3 | {1:1, 2:1, 3:1} | drop 1 | 1 | yes, {2:1, 3:1} | 2 |
| 3 | 2 | {2:2, 3:1} | no | 1 | yes | 3 |
| 4 | 2 | {2:3, 3:1} | no | 1 | yes | 4 |
Complexity. Time O(n): one pass with constant work per tree. Space O(1): at most three keys in the map.
Because 0 ≤ fruits[i] < n, you can also replace the map with an int[n] count array plus a distinct counter. That avoids boxing and is noticeably faster in Java, at the price of O(n) space. The map version is shown because it works for any value range.
Edge cases and pitfalls
- One tree.
[5]gives1. - Only one or two types overall. The whole array is the answer; the window never needs to move
l. - Removing a type too early. Never call
count.remove(f)just becausefleft the window once; other copies offmay still be inside. Remove the key only when its count reaches zero, otherwisecount.size()under-counts the types. - Integer caching. Comparing the result of
mergewith== 0is safe because theIntegeris unboxed when compared with anintliteral. Comparing twoIntegerobjects with==would not be safe. - Recording invalid windows. In the never-shrinking version, only record the length when the window is valid.
Related problems
- Longest Substring With At Most K Distinct Characters is the same problem with
2replaced by anyk, on characters. - Max Consecutive Ones III uses the identical slide-instead-of-shrink trick with a count of zeros.
- Subarrays with K Different Integers counts windows with exactly
kdistinct values instead of measuring the longest. - Longest Substring Without Repeating Characters is another longest-valid-window problem.