Обработка последовательностей: максимум, сумма, фильтрация

Десяток задач из ЕГЭ и олимпиад сводятся к одному и тому же набору приёмов — освоим их раз и навсегда.

Накопитель — переменная, которая постепенно собирает результат (сумму, максимум, счётчик) по мере прохода по данным.

Зачем это нужно

«Дана последовательность чисел…» — так начинается едва ли не половина задач по программированию. Найти сумму, максимум, количество подходящих элементов, отфильтровать нужные — это базовые операции, из которых складываются решения посложнее. Если довести их до автоматизма, любая такая задача перестаёт пугать: вы сразу видите подходящий шаблон. Этот урок — про приёмы-кирпичики.

За этими приёмами стоит важное наблюдение: огромное число задач сводится к одному проходу по данным с одной или несколькими «переменными-памятью». Профессионалы называют такой проход линейным, и он ценен тем, что обрабатывает каждый элемент ровно один раз — а значит, работает быстро даже на больших объёмах. Новичок, столкнувшись с задачей «найти максимум и сколько раз он встретился», нередко делает два прохода (сначала ищет максимум, потом считает повторы) или вовсе сортирует массив. Но почти всегда хватает одного прохода, если заранее завести нужные накопители. Поэтому, читая условие, полезно сразу спросить себя: «какие величины я хочу получить, и какие переменные надо обновлять на каждом шаге, чтобы собрать их за один проход?» Этот вопрос превращает расплывчатую задачу в чёткий план — и именно ему посвящён урок.

Шаблон «накопитель»: сумма и количество

Идея проста: заводим переменную-накопитель, проходим по элементам и обновляем её. Для суммы накопитель начинают с нуля, для произведения — с единицы. Считаем сумму всех чисел и количество чётных одним проходом:

data = [7, 2, 9, 4, 6, 11, 8, 3]

total = 0          # накопитель суммы
even_count = 0     # накопитель счётчика
for x in data:
    total += x
    if x % 2 == 0:
        even_count += 1

print("сумма:", total)
print("чётных:", even_count)
print("среднее:", round(total / len(data), 2))   # 8 чисел

Вывод:

сумма: 50
чётных: 4
среднее: 6.25

Поиск максимума и его позиции

Классический приём: считаем первый элемент «пока что лучшим» и сравниваем с остальными. Важная тонкость — часто нужен не только сам максимум, но и его индекс. Найдём максимум, минимум и позицию максимума вручную (понимая, что внутри делают встроенные max/min):

data = [4, 8, 15, 16, 23, 42, 19]

best = data[0]        # пока лучший — первый
best_index = 0
for i in range(1, len(data)):
    if data[i] > best:
        best = data[i]
        best_index = i

print("максимум:", best, "на позиции", best_index)
print("проверка встроенным:", max(data), data.index(max(data)))
print("минимум:", min(data))

Вывод:

максимум: 42 на позиции 5
проверка встроенным: 42 5
минимум: 4

Фильтрация: оставить только нужное

Фильтрация — отбор элементов по условию. В Python это компактно делает генератор списка. Соберём числа, делящиеся на 3, и отдельно — те, что больше среднего:

data = [12, 7, 9, 20, 3, 15, 8, 6]
avg = sum(data) / len(data)

div3 = [x for x in data if x % 3 == 0]
above_avg = [x for x in data if x > avg]

print("среднее:", round(avg, 2))
print("кратные 3:", div3)
print("больше среднего:", above_avg)
print("сумма кратных 3:", sum(div3))

Вывод:

среднее: 10.0
кратные 3: [12, 9, 3, 15, 6]
больше среднего: [12, 20, 15]
сумма кратных 3: 45

Два максимума: ищем второй по величине

Частая ловушка: найти второй по величине элемент. Наивно отсортировать и взять предпоследний — но это лишняя работа O(n log n), когда хватает одного прохода O(n). Держим два накопителя — первый и второй максимумы:

data = [4, 42, 15, 42, 23, 8]

first = second = float("-inf")   # минус бесконечность как старт
for x in data:
    if x > first:
        second = first           # бывший первый стал вторым
        first = x
    elif x > second and x != first:
        second = x

print("первый максимум:", first)
print("второй максимум:", second)

Вывод:

первый максимум: 42
второй максимум: 23

Скользящее окно: суммы соседних элементов

Иногда нужно работать не с отдельными элементами, а с группами подряд идущих — например, найти максимальную сумму трёх соседних. Это приём «скользящего окна»: рассматриваем фрагменты фиксированной длины, сдвигая их по одному:

data = [1, 3, 2, 6, 1, 4, 1, 8, 2]
k = 3                            # размер окна

best_sum = sum(data[0:k])
best_pos = 0
for i in range(1, len(data) - k + 1):
    window_sum = sum(data[i:i + k])
    if window_sum > best_sum:
        best_sum = window_sum
        best_pos = i

print(f"макс. сумма {k} соседних = {best_sum}")
print("это элементы:", data[best_pos:best_pos + k])

Вывод:

макс. сумма 3 соседних = 13
это элементы: [4, 1, 8]

Попробуй сам

Соберём типовую задачу целиком: дана строка чисел, нужно вывести их количество, сумму положительных, максимум по модулю и список чисел, кратных 5. Это сразу несколько шаблонов в одном.

raw = "12 -7 0 25 -30 8 15 -3"
nums = [int(x) for x in raw.split()]

pos_sum = sum(x for x in nums if x > 0)
max_abs = max(nums, key=abs)
mult5 = [x for x in nums if x % 5 == 0]

print("количество:", len(nums))
print("сумма положительных:", pos_sum)
print("максимум по модулю:", max_abs)
print("кратные 5:", mult5)

Вывод:

количество: 8
сумма положительных: 60
максимум по модулю: -30
кратные 5: [0, 25, -30, 15]

Частые ошибки

  • Неверный старт накопителя. Для суммы старт 0, для произведения 1, для максимума — первый элемент или минус бесконечность (но не 0: вдруг все числа отрицательные).
  • Инициализируют максимум нулём. Если все элементы отрицательные, ноль окажется «максимумом» ошибочно.
  • Сортируют там, где хватит одного прохода. Для максимума/минимума не нужна сортировка — это лишняя сложность.
  • Путают «больше» и «больше или равно» при поиске второго максимума. Дубликаты максимума требуют аккуратной проверки.

Итоги

  • Накопитель собирает результат за один проход: сумма (старт 0), произведение (старт 1), счётчик.
  • Максимум ищут сравнением с «пока лучшим»; для второго максимума держат два накопителя.
  • Фильтрация компактно делается генератором списка с условием.
  • Скользящее окно обрабатывает группы подряд идущих элементов.
Проверьте себя
1. С какого значения правильно начинать накопитель при поиске максимума, если числа могут быть отрицательными?
AС нуля
BС первого элемента последовательности (или минус бесконечности)
CС единицы
DС самого большого возможного числа
2. Почему для поиска максимума не нужна сортировка?
AСортировка не работает с числами
BДостаточно одного прохода O(n), а сортировка — это лишняя сложность O(n log n)
CМаксимум всегда последний элемент
DСортировка портит данные
3. Что делает приём «скользящее окно»?
AУдаляет дубликаты
BСортирует элементы
CОбрабатывает группы подряд идущих элементов фиксированной длины, сдвигая их по одному
DИщет элемент по значению

Закрепите практикой

Задачи с автоматической проверкой — решайте прямо здесь, не уходя из учебника.

Поддержать проект