Есть много способов решить эту проблему.Вот пара вариантов.В дальнейшем я буду обозначать число элементов n во входном массиве, а w - размер окна.
Вариант 1: простой алгоритм O (n log w) -time
Один из вариантов - сохранить сбалансированное двоичное дерево поиска, содержащее все элементы в текущем окне, включая дубликаты.Для вставки чего-либо в этот BST потребовалось бы время O (log w), поскольку в окне всего w элементов, а удаление элемента также заняло бы время O (log w) по той же причине.Это означает, что скольжение окна на одну позицию занимает время O (log w).
Чтобы найти второй по величине элемент в окне, вам просто нужно применить стандартный алгоритм для поискавторой по величине элемент в BST , который занимает время O (log w) в BST с элементами w.
Преимущество этого подхода состоит в том, что в большинстве языков программирования это будет довольнопросто закодировать это.Это также использует кучу известных стандартных методов.Недостатком является то, что время выполнения не является оптимальным, и мы можем улучшить его.
Вариант 2: Алгоритм префикса / суффикса O (n)
Вот решение с линейным временем, которое относительнопросто реализовать.На высоком уровне решение работает, разбивая массив на ряд блоков, каждый из которых имеет размер w.Например, рассмотрим следующий массив:
31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 95 02 88 41 97
Представьте, что w = 5. Мы разделим массив на блоки размером 5, как показано здесь:
31 41 59 26 53 | 58 97 93 23 84 | 62 64 33 83 27 | 95 02 88 41 97
Теперь,представьте, что в этом массиве нужно разместить окно длиной 5, как показано здесь:
31 41 59 26 53 | 58 97 93 23 84 | 62 64 33 83 27 | 95 02 88 41 97
|-----------------|
Обратите внимание, что это окно всегда будет состоять из суффикса одного блока, за которым следует префикс другого.Это хорошо, потому что позволяет нам решить несколько более простую проблему.Представьте, что каким-то образом мы можем эффективно определить два самых больших значения в любом префиксе или суффиксе любого блока.Тогда мы могли бы найти значение second-max в любом окне следующим образом:
- Выяснить, какому префиксу блоков и суффиксу соответствует окно.
- Получить два верхних элемента из каждогоиз этих префиксов и суффиксов (или только одного верхнего элемента, если окно достаточно маленькое).
- Из этих (до) четырех значений определите, какое является вторым по величине, и верните его.
С небольшой предварительной обработкой мы действительно можем настроить наши окна для ответа на запросы вида "каковы два самых больших элемента в каждом суффиксе?"и "каковы два самых больших элемента в каждом префиксе?"Вы можете рассматривать это как проблему динамического программирования, настроенную следующим образом:
- Для любого префикса / суффикса длины один, сохраните одно значение в этом префиксе / суффиксе.
- Для любого префикса / суффикса длины два верхние два значения - это сами два элемента.
- Для любого более длинного префикса или суффикса этот префикс или суффикс можно сформировать, расширив меньший префикс или суффикс наодин элементЧтобы определить два верхних элемента этого более длинного префикса / суффикса, сравните элемент, используемый для расширения диапазона, до двух верхних элементов и выберите два верхних из этого диапазона.
Обратите внимание, что заполнение каждогоДва верхних значения префикса / суффикса занимают время O (1).Это означает, что мы можем заполнить любое окно за время O (w), так как есть w записей для заполнения. Более того, поскольку существует всего O (n / w) окон, общее время, необходимое для заполнения этих записей, составляет O(n), поэтому наш общий алгоритм работает за время O (n).
Что касается использования пространства: если вы с нетерпением вычисляете все значения префикса / суффикса во всем массиве, вам нужно использовать пространство O (п) держать все.Однако, поскольку в любой момент времени мы заботимся только о двух окнах, вы можете альтернативно вычислять префиксы / суффиксы только тогда, когда они вам нужны.Для этого потребуется только пробел O (w), что действительно очень хорошо!
Вариант 3: решение за O (n) время с использованием умных структур данных
Этот последний подход оказывается полностью эквивалентным вышеописанному подходу, но формирует его по-другому.
Можно построить очередь, которая позволяет в постоянное время запрашивать его максимальный элемент . Идея этой очереди - начиная с стека, который поддерживает эффективный find-max , а затем используя его в построении очереди с двумя стеками, - можно легко обобщить для создания очереди, которая дает постоянный доступ к второй по величине элемент. Для этого нужно просто адаптировать конструкцию стека для хранения двух верхних элементов в каждый момент времени, а не только самого большого элемента.
Если у вас есть очередь, подобная этой, алгоритм поиска значения second-max в любом окне довольно быстрый: загрузите очередь с первыми элементами w, а затем несколько раз снимите с очереди элемент (сдвиньте что-нибудь из окна) и поставьте в очередь следующий элемент (сдвиньте что-нибудь в окно). Каждая из этих операций занимает амортизированное O (1) время для завершения, поэтому это занимает время O (n) в целом.
Забавный факт - если вы посмотрите на то, что на самом деле делает эта реализация очереди в данном конкретном случае, вы обнаружите, что она полностью эквивалентна приведенной выше стратегии. Один стек соответствует суффиксам предыдущего блока, а другой - префиксам следующего блока.
Эта последняя стратегия - мой личный фаворит, но по общему признанию, это - только мое собственное смещение структур данных.
Надеюсь, это поможет!