904. Fruits into Basket - Explanation

Problem Link

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: 3

Explanation: We can pick from all trees.

Example 2:

Input: fruits = [0,1,2,2]

Output: 3

Explanation: 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: 4

Explanation: 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,000
  • 0 <= fruits[i] < fruits.length


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



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

  1. For each starting index i:
    • Initialize an empty set types to track distinct fruit types.
    • Extend j from i while we have fewer than two types or the current fruit is already in our types set.
    • Add each fruit to the set and increment j.
    • Record the length j - i.
  2. Return the maximum length found.
class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        n = len(fruits)
        res = 0
        for i in range(n):
            types = set()
            j = i
            while j < n and (len(types) < 2 or fruits[j] in types):
                types.add(fruits[j])
                j += 1
            res = max(res, j - i)
        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1)

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

  1. Initialize a hash map count, left pointer l = 0, and res = 0.
  2. For each right index r:
    • Add fruits[r] to the count map.
    • While the map has more than two keys:
      • Decrement count[fruits[l]].
      • If it becomes zero, remove that key.
      • Increment l.
    • Update res with the current window size.
  3. 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) 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

  1. Initialize a hash map count and left pointer l = 0.
  2. For each right index r:
    • Add fruits[r] to the count map.
    • If the map has more than two keys:
      • Decrement count[fruits[l]].
      • If it becomes zero, remove that key.
      • Increment l.
  3. Return n - l as 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) - l

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) 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

  1. Track fruit1, fruit2, and their last indices fruit1_lastIdx, fruit2_lastIdx.
  2. Initialize l = 0 and res = 1.
  3. For each index r:
    • If fruits[r] matches fruit1, update fruit1_lastIdx.
    • Else if it matches fruit2 (or fruit2 is unset), update accordingly.
    • Else (third type found):
      • The fruit with the smaller last index gets replaced.
      • Update l to be one past that smaller index.
      • Set the new fruit and its index.
    • Update res with r - l + 1.
  4. 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) 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.