Постановка задачи(официальная)
Вы посещаете ферму, на которой деревья выстроены в один ряд слева направо. Деревья представлены целочисленным массивом fruits, где fruits[i] — это тип фруктов на i-ом дереве. 
Вы хотите собрать как можно больше фруктов, но владелец фермы установил следующие правила:
- У вас есть две корзины, каждая из которых может содержать только один тип фруктов.
- В каждой корзине может быть неограниченное количество фруктов.
- Начиная с любого дерева, вы должны собирать фрукты с каждого дерева (включая стартовое), двигаясь вправо.
- Если вы встречаете дерево, фрукты которого не могут поместиться в ваши корзины, вы должны остановиться.
Задача состоит в том, чтобы найти максимальное количество фруктов, которые можно собрать, соблюдая эти правила.
Упрощенная версия постановки
Дан массив fruits, где каждый элемент представляет дерево, а его значение указывает тип фрукта, который растет на этом дереве. У нас есть две корзины, каждая из которых может содержать неограниченное количество фруктов одного типа(к примеру, в одну корзину можем положить все фрукты типа 4, а во вторую корзину положить все фрукты типа 7). Наша цель — собрать как можно больше фруктов, непрерывно двигаясь слева направо от любого начального дерева, пока не встретим тип фрукта, который не помещается в корзины(к примеру, в одной корзине фрукты типа 4, в другой корзине фрукты типа 7, а сейчас мы рядом с деревом на котором растет фрукт типа 2, мы не можем добавить этот тип ни в одну корзину, т.к. в корзине может находиться фрукты только одного типа).
Подход к решению
Для решения задачи мы будем использовать технику "скользящего окна" (sliding window) и хэш-таблицу для отслеживания количества каждого типа фруктов в текущем окне.
Алгоритм
- 
Инициализация переменных: - 
resдля хранения результата (максимальное количество фруктов).
- 
type_counterдля отслеживания количества каждого типа фруктов в текущем окне.
- 
leftдля обозначения левой границы окна.
 
- 
- 
Проход по массиву fruits:- Используем переменную rightдля обозначения правой границы окна.
- Увеличиваем счетчик для текущего типа фрукта right_fruit.
 
- Используем переменную 
- 
Проверка условия допустимости окна: - Если количество типов фруктов в окне становится больше двух (len(type_counter) == 3), сдвигаем левую границу окнаleftвправо, уменьшая счетчики для фруктов и удаляя типы фруктов с нулевым счетчиком.
 
- Если количество типов фруктов в окне становится больше двух (
- 
Обновление результата: - На каждой итерации обновляем res, вычисляя длину текущего окна.
 
- На каждой итерации обновляем 
Код решения
from collections import defaultdict
from typing import List
class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        res = 0
        type_counter = defaultdict(int)
        left = 0
        for right in range(len(fruits)):
            right_fruit = fruits[right]
            type_counter[right_fruit] += 1
            while len(type_counter) == 3:
                left_fruit = fruits[left]
                type_counter[left_fruit] -= 1
                if type_counter[left_fruit] == 0:
                    del type_counter[left_fruit]
                left += 1
            res = max(res, right - left + 1)
        return res
Объяснение кода
- 
Инициализация: - 
res- переменная для хранения максимального количества фруктов.
- 
type_counter- хэш-таблица для отслеживания количества каждого типа фруктов в текущем окне.
- 
left- индекс левой границы окна.
 
- 
- 
Основной цикл: - Проходим по массиву fruitsс правой границей окнаright.
- Увеличиваем счетчик для текущего типа фрукта right_fruit.
 
- Проходим по массиву 
- 
Уменьшение окна: - Если количество типов фруктов в окне превышает два (len(type_counter) == 3), сдвигаем левую границу окна вправо до тех пор, пока количество типов фруктов не станет допустимым (два или меньше).
 
- Если количество типов фруктов в окне превышает два (
- 
Обновление результата: - На каждой итерации обновляем res, вычисляя текущую длину окна (right - left + 1).
 
- На каждой итерации обновляем 
Визуализация решения
Рассмотрим массив fruits = [1, 2, 1, 2, 3, 3, 2, 1] и визуализируем шаги решения задачи, показывая текущее состояние скользящего окна.
Шаги выполнения:
Итерация 0:
- Текущий фрукт: 1
- Окно: [1], 2, 1, 2, 3, 3, 4, 1, 2
- type_counter: {1: 1}
- Длина окна: 1
Итерация 1:
- Текущий фрукт: 2
- Окно: [1, 2], 1, 2, 3, 3, 4, 1, 2
- type_counter: {1: 1, 2: 1}
- Длина окна: 2
Итерация 2:
- Текущий фрукт: 1
- Окно: [1, 2, 1], 2, 3, 3, 4, 1, 2
- type_counter: {1: 2, 2: 1}
- Длина окна: 3
Итерация 3:
- Текущий фрукт: 2
- Окно: [1, 2, 1, 2], 3, 3, 4, 1, 2
- type_counter: {1: 2, 2: 2}
- Длина окна: 4
Итерация 4:
- Текущий фрукт: 3
- Окно: [1, 2, 1, 2, 3], 3, 4, 1, 2
- type_counter: {1: 2, 2: 2, 3: 1}
- Длина окна: 5
- Сокращение окна:
- left: 1
- Окно после сокращения: 1, [2, 1, 2, 3], 3, 4, 1, 2
- type_counter: {1: 1, 2: 2, 3: 1}
- Длина окна: 4
- left: 2
- Окно после сокращения: 1, 2, [1, 2, 3], 3, 4, 1, 2
- type_counter: {1: 1, 2: 1, 3: 1}
- Длина окна: 3
- left: 3
- Окно после сокращения: 1, 2, 1, [2, 3], 3, 4, 1, 2
- type_counter: {2: 1, 3: 1}
- Длина окна: 2
 
- Окно после обновления результата: 1, 2, 1, [2, 3], 3, 4, 1, 2
- Текущий максимум: 4
Итерация 5:
- Текущий фрукт: 3
- Окно: 1, 2, 1, [2, 3, 3], 4, 1, 2
- type_counter: {2: 1, 3: 2}
- Длина окна: 3
Итерация 6:
- Текущий фрукт: 4
- Окно: 1, 2, 1, [2, 3, 3, 4], 1, 2
- type_counter: {2: 1, 3: 2, 4: 1}
- Длина окна: 4
- Сокращение окна:
- left: 4
- Окно после сокращения: 1, 2, 1, 2, [3, 3, 4], 1, 2
- type_counter: {3: 2, 4: 1}
- Длина окна: 3
 
- Окно после обновления результата: 1, 2, 1, 2, [3, 3, 4], 1, 2
- Текущий максимум: 4
Итерация 7:
- Текущий фрукт: 1
- Окно: 1, 2, 1, 2, [3, 3, 4, 1], 2
- type_counter: {3: 2, 4: 1, 1: 1}
- Длина окна: 4
- Сокращение окна:
- left: 5
- Окно после сокращения: 1, 2, 1, 2, 3, [3, 4, 1], 2
- type_counter: {3: 1, 4: 1, 1: 1}
- Длина окна: 3
- left: 6
- Окно после сокращения: 1, 2, 1, 2, 3, 3, [4, 1], 2
- type_counter: {4: 1, 1: 1}
- Длина окна: 2
 
- Окно после обновления результата: 1, 2, 1, 2, 3, 3, [4, 1], 2
- Текущий максимум: 4
Итерация 8:
- Текущий фрукт: 2
- Окно: 1, 2, 1, 2, 3, 3, [4, 1, 2]
- type_counter: {4: 1, 1: 1, 2: 1}
- Длина окна: 3
- Сокращение окна:
- left: 7
- Окно после сокращения: 1, 2, 1, 2, 3, 3, 4, [1, 2]
- type_counter: {1: 1, 2: 1}
- Длина окна: 2
 
- Окно после обновления результата: 1, 2, 1, 2, 3, 3, 4, [1, 2]
- Текущий максимум: 4
После всех итераций максимальное количество фруктов, которые можно собрать, равно 4.
Асимптотическая сложность
Сложность по времени: O(n)
- Мы проходим по массиву fruitsодин раз с помощью правой границы окна (right).
- В худшем случае левая граница окна (left) также проходит по каждому элементу массива один раз.
- В результате, каждый элемент массива обрабатывается не более двух раз, что приводит к линейной временной сложности O(n), где n— длина массиваfruits.
Сложность по памяти: O(1)
- Мы используем хэш-таблицу type_counterдля хранения количества каждого типа фруктов в текущем окне.
- Максимальное количество различных типов фруктов в хэш-таблице ограничено двумя, так как у нас есть только две корзины.
- Следовательно, количество памяти, необходимой для хранения данных в хэш-таблице, не зависит от размера входного массива и остается постоянным.
- Это приводит к постоянной сложности по памяти O(1).
 
![Cover image for Решение задачи с собеседования Fruit Into Baskets [+ ВИДЕО]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16n230fv7vjs21jeb2uw.jpg) 
              
 
    
Top comments (0)