DSA sheet · Arrays · Sliding Window pattern

Fruits Into Baskets

This problem hides a sliding window inside a story about a farm. Once you see through the story, it asks: "what is the longest subarray with at most 2 different values?" The teacher first writes a brute force that restarts from every tree with a fresh hashmap. She explains where to restart after getting stuck, and why a hashmap with counts is needed instead of a set. Then she turns it into a sliding window that shrinks from the left instead of restarting.

Why it matters: this is the first window in the sheet whose rule is about how many distinct values are inside, so the window's "summary" is a frequency map instead of a sum. The same shape solves Longest Substring with K Unique Characters and is the building block of Subarrays with K Different Integers.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a subarray?

A subarray is a piece of the array whose elements are next to each other (contiguous): a start index, an end index, and everything in between, with nothing skipped. (In a string, the same thing is a substring.) Its length is end − start + 1. In this problem, "picking fruit from consecutive trees" is exactly choosing a subarray.

index01234
fruits12322[2, 3, 2] is a subarray
fruits12322[1, 2] from index 0 and 3 is not

The brute force over all windows, and why it repeats work

Two loops can try every start and stretch to the right from it: O(n²). The waste: if we stretch from index 0 over [1, 1, 2] and get stuck, the next start (index 1) re-adds 1 and 2, which we had already put into our map. Sliding window keeps what's already in the window and only fixes the edges.

The window [left..right]: expand and shrink

A window is the current subarray between two pointers, left and right. We keep a summary of what's inside so we don't re-scan it.

Both pointers start at index 0 and only ever move forward.

Fixed-size vs variable-size windows

Fixed-size windowVariable-size window
question looks like"every subarray of size k…""longest / shortest / how many subarrays such that…"
how it movesadd the new element; once size passes k, remove the one k steps backgrow until the rule breaks, then shrink until it holds again
this problem?no size is givenyes

Longest vs shortest vs count

kindshrink while…record the answer…
longest valid (this problem)the window is invalidafter shrinking: best = max(best, right − left + 1)
shortest validthe window is still validinside the while, before removing: best = min(…)
count of validthe window is invalidafter shrinking: count += right − left + 1

For "exactly K different values" (problem 8 in this sheet) the trick is exactly(K) = atMost(K) − atMost(K − 1). This problem is already an "at most 2", so we don't need it.

Why the rule must be monotonic

Sliding window works only if adding an element can only make the window worse or equal, and removing one can only make it better or equal. Here the rule is "at most 2 distinct values": adding a tree can only keep or increase the number of types, and removing one can only keep or decrease it. So it's safe. (For sums this is why numbers must be positive. A negative number would break it.)

Frequency maps (dict / Counter)

A frequency map stores value → how many times it appears (in the window). In Python: a dict with freq.get(x, 0) + 1, or collections.Counter / defaultdict(int). len(freq) is the number of distinct values, as long as we delete keys whose count drops to 0.

a frequency map in a few lines
freq = {}
for x in [1, 1, 2, 1]:
    freq[x] = freq.get(x, 0) + 1     # {1: 3, 2: 1} -> 2 distinct types
freq[2] -= 1                         # {1: 3, 2: 0}
if freq[2] == 0:
    del freq[2]                      # {1: 3} -> now len(freq) == 1

The teacher uses Java's map.getOrDefault(key, 0) + 1, which is the same as Python's freq.get(key, 0) + 1. One more tool, only for Sliding Window Maximum: the monotonic deque, which we don't need here.

The teacher's 4 array patterns

patternwhen it fits
Two pointersonly two particular points matter, nothing in between
Sliding windowtwo points and everything in between matter
Prefix sumcontinuous sums between two indexes, usually for many queries ("sum from index 2 to 3?")
Kadane'smaximum sum when numbers can be negative

Part A · Brute force: start at every tree, fresh map each time

LeetCode 904

1The question in simple words

A farm has one row of fruit trees, left to right. fruits[i] is the type of fruit on tree i (a number). You want to collect as many fruits as possible, but the owner has rules:

Return the most fruits you can pick.

The story, translatedWalking right without skipping = a subarray. Two baskets, one type each = at most 2 distinct values in it. One fruit per tree = the answer is the subarray's length. So: longest subarray with at most 2 distinct values.
exampleanswerwhy
[1, 2, 1]31 goes in basket 1, 2 in basket 2, the next 1 back in basket 1. Only 2 types, so we take all 3.
[0, 1, 2, 2]3[0, 1] is fine, but [0, 1, 2] has 3 types. Best is [1, 2, 2].
[1, 2, 3, 2, 2]4[2, 3, 2, 2] (the teacher's main example).

The teacher notices: since the rule is about counting types, a hashmap will probably help. She comes back to that soon.

2What the constraints tell us

3Intuition: try every starting tree

Stand at a tree, walk right picking fruit, and keep track of which types are in the baskets. When a third type shows up, stop and note how many fruits you got. Then try again from the next tree. The best of all these tries is the answer.

4Building the logic from examples

Walk it on [1, 2, 3, 2, 2]

Start at index 0: take 1 (basket A), take 2 (basket B) → 2 fruits. Next is 3, a third type → stop. Best so far: 2.

Where do we restart: at the 3 where we got stuck, or at the next tree after the old start?

The teacher asks this on purpose. Restarting at the 3 (index 2) looks tempting, because we stopped there. But then the best we'd find is [3, 2, 2] = 3 fruits. The real best is [2, 3, 2, 2] = 4, which starts at index 1, before the 3. So the brute force must restart at old start + 1, not where it got stuck.

restart at 3123223 fruits ✗ misses the best
start + 1123224 fruits ✓

What do we store the baskets in?

"How many different types do I have?" is a question about unique values. For unique values we normally reach for a set or a hashmap. The teacher picks a hashmap (type → count). For the brute force alone, a set would also do (we never take anything out). But the count will be essential once we start removing fruits in Part B, so she uses the map from the start. Her set vs map example is explained in Part B.

Building the inner loop

After the inner loop ends (by break or by reaching the end), update ans = max(ans, count).

Doubt 1: why reset the map and the count inside the outer loop?
→ Every new start is a new attempt with empty baskets. If the map from the previous start stayed, its old types would block fruits that are actually fine for this start.
Doubt 2: her pointer names change mid-way. Which is which?
→ She first calls the outer pointer r, then renames it left (it "waits" at the start), and calls the inner one right (it walks and collects). Outer = left = start, inner = right = the tree being picked.

5Approach steps

  1. ans = 0.
  2. For every start left: new empty map, count = 0.
  3. For right from left to the end: add fruits[right] to the map.
  4. If the map has more than 2 types → break. Else count += 1.
  5. After the inner loop: ans = max(ans, count).
  6. Return ans.

6Code (Python)

Brute force: O(n²), TLE for n = 10⁵
class Solution:
    def totalFruit(self, fruits):
        n = len(fruits)
        ans = 0
        for left in range(n):                    # try every starting tree
            basket = {}                          # fresh baskets: type -> count
            count = 0                            # fruits picked from this start
            for right in range(left, n):
                f = fruits[right]
                basket[f] = basket.get(f, 0) + 1
                if len(basket) > 2:              # a third type: must stop
                    break
                count += 1
            ans = max(ans, count)
        return ans

7Code line by line

linewhat it means
for left in range(n):Every tree gets a turn as the starting point.
basket = {} count = 0New attempt: empty baskets, nothing picked yet.
for right in range(left, n):Walk right from the start, one tree at a time.
basket[f] = basket.get(f, 0) + 1Put the fruit in: new type starts at 0 + 1, known type goes up by 1.
if len(basket) > 2: breakThe number of keys = the number of types. More than 2 means this fruit doesn't fit → stop this attempt. We don't count it.
count += 1The fruit fit, so it's collected.
ans = max(ans, count)After the attempt ends, keep the best total.

8Dry run (hand table)

fruits = [1, 2, 3, 2, 2].

start lefttrees picked (map after each)why it stoppedcountans after
01 {1:1} → 2 {1:1, 2:1}3 makes {1,2,3} → 3 types → break22
12 {2:1} → 3 {2:1, 3:1} → 2 {2:2, 3:1} → 2 {2:3, 3:1}end of array44
23 → 2 → 2end of array34
32 → 2end of array24
42end of array14

Final answer: 4 ✓. Notice how start 1 re-added 2 and start 2 re-added 3, 2, 2: the repeated work the sliding window removes.

9Complexity & remember

Remember the brute forceFor each start: empty map, walk right adding types, break when a third type arrives, keep the max count. Restart at start + 1, not where you got stuck.

Part B · Optimal: sliding window with a frequency map

LeetCode 904

1The question again, with the new goal

Same question: the longest run of consecutive trees with at most 2 types. Goal: O(n), with left never restarting.

2Choosing the pattern

3Intuition: don't restart, just drop trees from the left

Look at [1, 1, 2, 3, 3]. The brute force goes 1, 1, 2, gets stuck at 3, then restarts at index 1 and re-adds 1 and 2, which were already in the map. Why recompute them?

Sliding window's idea instead:

On [1, 1, 2, 3, 3]: when 3 arrives, the window is [1, 1, 2, 3] with 3 types. Drop the first 1 → [1, 2, 3], still 3 types. Drop the second 1 → [2, 3], 2 types ✓. Stop shrinking. Then right picks up the last 3 → [2, 3, 3], length 3.

4Building the logic from examples

Why a hashmap with counts, not a set (her [1, 1, 2, 1, 3] example)

Say the window is [1, 1, 2, 1] and the next tree is 3. Now we must shrink from the left until there are only 2 types.

removewith a set {1, 2, 3}with a map {1:3, 2:1, 3:1}
first 1remove 1 → {2, 3} ✗ but two more 1s are still in the window!1's count 3 → 2 → {1:2, 2:1, 3:1}, still 3 types
second 1(already wrong)1's count 2 → 1 → still 3 types
the 22's count 1 → 0 → delete 2 → {1:1, 3:1}, 2 types ✓

The window is now [1, 3], and type 1 is still needed (one 1 is still inside). A set can only say "is 1 present: yes/no", so removing one 1 removes the whole type. The map knows how many 1s are inside, so the type only disappears when its count reaches 0. That's why the frequency count is required.

The shrinking step, piece by piece

With a sum we "removed" by subtracting. Here we "added" by increasing a count, so we remove by decreasing it:

  1. basket[fruits[left]] -= 1 (one fewer of that type).
  2. If that count is now 0, delete the key: the type is completely gone from the window.
  3. left += 1.
Doubt 1: why must the key be deleted when its count hits 0? Isn't a 0 harmless?
→ We check the number of types with len(basket), which counts keys, including ones with value 0. A leftover {2: 0} would still count as a type and the window would think it has 3 types when it really has 2.
Doubt 2: if or while for the shrink?
→ while. The teacher first writes an if, then changes it. One removal may not drop a whole type (in the example above it took 3 removals). We keep shrinking until len(basket) ≤ 2 again.
Doubt 3: she sometimes says "exactly two types". Must there be exactly two?
→ No, at most two. One type is fine (both baskets aren't required). For [1, 1, 1] the answer is 3, and the code handles it: the map has 1 key, which is never > 2.

Recording the answer

After the while loop, the window is valid again (≤ 2 types), so ans = max(ans, right − left + 1). This is a "longest valid window" problem, so we record after shrinking.

5Approach steps

  1. left = 0, ans = 0, empty map basket (made once, before the loop).
  2. For each right: add fruits[right] to the map (expand).
  3. While the map has more than 2 types: decrease the count of fruits[left], delete it if 0, left += 1 (shrink).
  4. ans = max(ans, right − left + 1).
  5. Return ans.

6Code (Python)

Optimal: sliding window + frequency map, O(n)
class Solution:
    def totalFruit(self, fruits):
        basket = {}                              # type -> how many in the window
        left = 0
        ans = 0
        for right in range(len(fruits)):
            f = fruits[right]
            basket[f] = basket.get(f, 0) + 1     # expand: pick this fruit
            while len(basket) > 2:               # third type: shrink from the left
                g = fruits[left]
                basket[g] -= 1
                if basket[g] == 0:
                    del basket[g]                # type completely gone
                left += 1
            ans = max(ans, right - left + 1)     # window is valid again
        return ans

7Code line by line

linewhat it means
basket = {} left = 0 ans = 0The map is created once, outside the loop. Unlike the brute force, it is never reset: it always describes the current window.
for right in range(len(fruits)):The expansion pointer visits every tree once.
basket[f] = basket.get(f, 0) + 1Add the new fruit to the window's counts.
while len(basket) > 2:More than 2 types in the window → invalid → keep shrinking.
basket[g] -= 1The leftmost fruit leaves the window: one fewer of its type.
if basket[g] == 0: del basket[g]No more of that type inside, so remove the key, otherwise len would still count it.
left += 1The window now starts one tree later.
ans = max(ans, right - left + 1)The window is valid. Its length is the number of fruits picked. Keep the biggest.

8Dry run

fruits = [1, 2, 3, 2, 2] (the teacher's example).

stepright (value added)window [left..right]map after addingshrink? (what leaves, why)window after shrinkingans so far
10 (1)[1] (0..0){1:1}no, 1 type[1] (0..0)1
21 (2)[1,2] (0..1){1:1, 2:1}no, 2 types[1,2] (0..1)2
32 (3)[1,2,3] (0..2){1:1, 2:1, 3:1}yes, 3 types: 1 leaves (count 0 → delete) → {2:1, 3:1}[2,3] (1..2)2
43 (2)[2,3,2] (1..3){2:2, 3:1}no, 2 types[2,3,2] (1..3)3
54 (2)[2,3,2,2] (1..4){2:3, 3:1}no, 2 types[2,3,2,2] (1..4)4

The array at the key moments (yellow = window, grey = left behind):

step 3123223 types → shrink
L R  
after123221 deleted → {2:1, 3:1}, length 2
 LR  
step 512322{2:3, 3:1}, length 4
 L  R

Final answer: 4 ✓, matching the brute force. In step 3 the window shrank by one tree and kept 2 and 3. The brute force would have thrown everything away and started again.

A second picture, her [1, 1, 2, 1, 3] example, where the shrink needs three removals:

3 arrives11213{1:3, 2:1, 3:1} → 3 types
L   R
after112131→2, 1→1, 2→0 (deleted) → {1:1, 3:1}
   LR

For [1, 1, 2, 1, 3] the best answer is 4 ([1, 1, 2, 1]), recorded at step 4, before the 3 arrived.

9Complexity & remember

She shows the final code accepted as one of the fastest solutions.

Remember the optimal for each right: basket[f] += 1 → while len(basket) > 2: decrement fruits[left], delete at 0, left += 1 → ans = max(ans, right − left + 1).
Counts (not a set), because a type may still be in the window after one copy leaves.

Part C · Revision page

Brute forceSliding window
ideafor every start, walk right until a 3rd type, keep the best countexpand right, and when a 3rd type appears, shrink left until 2 types remain
mapnew map for every startone map for the whole run, always = current window
after getting stuckrestart at start + 1 (re-adding fruits)drop fruits from the left only
time / spaceO(n²) / O(1)O(n) (≈2n moves) / O(1)
patternwhy it does / doesn't fit
Two pointersonly two points matter there; here every tree between them matters
Prefix sumfor range sums and queries; nothing is summed here
Kadane'smax sum with negatives; values are 0…10⁵
Sliding windowcontiguous trees, rule "≤ 2 types" only gets worse when growing and better when shrinking
If you remember only 5 lines 1. The story = longest subarray with at most 2 distinct values.
2. Window summary = frequency map (type → count). len(map) = number of types.
3. Expand: count up. While more than 2 types: count down fruits[left], delete at 0, move left.
4. Record max(ans, right − left + 1) after shrinking.
5. left never restarts → O(n).
Mistakes to avoid ✗ using a set (it forgets that a type still has copies in the window)
✗ forgetting to delete a key whose count is 0 (len still counts it)
✗ if instead of while for shrinking
✗ reading it as "exactly 2 types" (1 type is fine)
✗ in the brute force, restarting where you got stuck instead of at start + 1
✗ in the brute force, not resetting the map for each new start
test it yourself (paste under either Solution above)
s = Solution()
print(s.totalFruit([1, 2, 1]))                    # 3
print(s.totalFruit([0, 1, 2, 2]))                 # 3
print(s.totalFruit([1, 2, 3, 2, 2]))              # 4
print(s.totalFruit([3, 3, 3, 1, 2, 1, 1, 2, 3, 3, 4]))  # 5
print(s.totalFruit([1, 1, 1]))                    # 3  (one type is fine)
print(s.totalFruit([0, 1, 2, 3]))                 # 2  (all different)
print(s.totalFruit([7]))                          # 1

Based on this video: Fruits Into Baskets | Sliding Window