Обработка серий
Серия — это подряд идущая группа элементов последовательности, обладающих одним признаком. В задачах требуется найти длину самой длинной серии, количество серий, их суммарную длину или другие характеристики; те же алгоритмы применяются к массивам и строкам.
Что такое серия
Серия образуется соседними элементами, для которых выполняется один и тот же признак: например, \(a_i > 0\), \(a_i = 5\), символ является буквой или два соседних числа равны. Серии разделяются элементами, не удовлетворяющими признаку. Если последовательность рассматривается как обработка последовательности, то серия — один из основных объектов подсчёта.
Серия по признаку \(P\) — максимальный непрерывный фрагмент элементов, для каждого из которых выполняется \(P\). Максимальность означает, что слева и справа от серии нет соседнего элемента с тем же признаком либо граница последовательности.
Чтобы обработать серии, достаточно хранить текущую длину серии и лучший или требуемый результат. При выполнении признака текущая серия продолжается, иначе её длина сбрасывается в ноль.
Если нужно найти максимальную серию, после каждого увеличения обновляют максимум. Подробное понятие максимальной серии разобрано на странице «Максимальная серия»; здесь сосредоточимся на общем шаблоне.
Два способа обработки
Есть два равноправных подхода. В первом для каждого элемента поддерживается длина текущей серии. Такой способ удобен, если нужен максимум, количество серий или сумма длин. Во втором серия обрабатывается целиком: находим её начало, затем последовательно пропускаем все элементы с тем же признаком.
- Счётчик текущей серии: при выполнении признака увеличиваем \(cur\), иначе присваиваем \(cur=0\).
- Переходы: новая серия начинается, когда текущий элемент удовлетворяет признаку, а предыдущий — нет.
- Границы: после цикла иногда нужно отдельно обработать серию, которая заканчивается последним элементом.
- Для строк признаком может быть условие
s[i] == 'A',s[i].isdigit()или принадлежность заданному набору символов.
| Что требуется найти | Как обновлять результат |
|---|---|
| Длину текущей серии | cur увеличивать или сбрасывать |
| Максимальную длину | best = max(best, cur) |
| Количество серий | увеличивать при начале новой серии |
| Суммарную длину серий | добавлять cur при завершении серии |
| Длину последней серии | сохранить cur после окончания цикла |
Количество и длина серий
Количество серий считают по началу каждой серии. Если элементы идут слева направо, серия начинается в позиции \(i\), когда \(P(a_i)\) истинно и либо \(i\) — первый индекс, либо \(P(a_{i-1})\) ложно. Это соответствует теме количество серий, которую полезно изучить заранее.
Квадратные скобки обозначают индикатор: он равен \(1\), если условие истинно, и \(0\) в противном случае. Если нужно найти сумму длин всех серий, достаточно считать количество элементов, удовлетворяющих признаку: каждый такой элемент входит ровно в одну серию.
Удобно добавить перед циклом «фиктивный» предыдущий элемент, не удовлетворяющий признаку. Но в школьных программах понятнее явно проверить первый элемент или начать цикл со второго, отдельно обработав первый.
В последовательности признака элемент чётный записано: 2, 4, 7, 8, 10, 11. Сколько серий?
Универсальный алгоритм
Пусть дана последовательность \(a_1,\ldots,a_n\) и признак \(P\). Перебираем элементы слева направо. При истинном признаке увеличиваем текущую длину, при ложном — завершаем серию и обнуляем счётчик. В зависимости от задачи в момент завершения можно обновить минимум, максимум, сумму или число элементов.
1cur = 0 2best = 0 3series = 0 4for x in a: 5 if P(x): 6 if cur == 0: 7 series += 1 8 cur += 1 9 best = max(best, cur) 10 else: 11 cur = 0
В Python условие P(x) заменяется конкретным выражением, например x > 0. В Паскале и C++ логика та же: важны не названия переменных, а порядок обновления состояния.
Разобранный пример
Дана последовательность: \(-2, 5, 7, 3, -1, 4, 6, -8, 9\). Найдём длину максимальной серии положительных чисел, количество таких серий и длину последней серии.
Максимальная длина положительной серии равна \(3\), количество положительных серий — \(3\), длина последней серии — \(1\). Обратите внимание: последнюю серию нельзя потерять, если результат обновляется только при встрече отрицательного элемента.
Серии в строках
Строку можно рассматривать как массив символов — см. страницу «Строка как массив символов». Например, в строке aaabbcaa серии одинаковых символов имеют длины \(3,2,1,2\). Для такого случая признак задаётся не отдельно для каждого символа, а сравнением с предыдущим: серия продолжается, если s[i] == s[i-1].
Другой вариант — серии символов одного класса: цифр, латинских букв, пробелов или символов из заданного набора. Тогда сначала определяют функцию-признак, а затем применяют тот же счётчик. При необходимости выделить фрагмент строки пригодятся срезы строки и приём обработки строки по символам.
1. Считать каждое подходящее число отдельной серией, не объединяя соседние элементы. 2. Не обновить максимум после последнего элемента. 3. Перепутать количество элементов и количество серий. 4. Сбросить cur до обновления результата при завершении серии. 5. В строке сравнивать символы как числа или забыть, что индексация начинается с нуля в Python.
Другие характеристики серий
По тому же шаблону находят серию минимальной или максимальной длины, число серий заданной длины, сумму первых элементов серий и длину серии, содержащей выбранный элемент. Для этого при завершении серии используют её накопленную длину и дополнительные переменные.
- Сначала определить признак серии и то, в какой момент серия завершается.
- Завести
curи переменные результата с безопасными начальными значениями. - При каждом элементе изменить
curсогласно признаку. - При завершении серии обновить характеристики; после цикла обработать незавершённую последнюю серию.
Продолжение серии увеличивает cur; разрыв серии обнуляет cur; начало серии увеличивает счётчик серий; конец серии удобен для подсчёта её характеристик.
Проверь себя
Главное
- Серия — максимальный непрерывный фрагмент элементов с одним признаком.
- Текущая длина увеличивается при продолжении серии и сбрасывается при разрыве.
- Количество серий считают по моментам их начала, а максимум длины обновляют после каждого подходящего элемента.
- Последнюю серию нужно обработать после цикла, если она заканчивается последним элементом.
- Для строк используются те же алгоритмы: меняется только условие, определяющее принадлежность символа серии.