«Оптимальный» трудно судить (почти невозможно) без профилирования.
Иногда «тупой» алгоритм может быть самым быстрым, потому что процессоры Intel невероятно быстры при сканировании тупых массивов на смежных блоках памяти, особенно если цикл и данные могут помещаться на кристалле. В отличие от этого, переход по указателям в большем блоке памяти, который не помещается на кристалле, может быть в десятки, сотни или в несколько раз медленнее.
У вас также могут возникнуть проблемы, когда вы пытаетесь распараллелить ваш код, если в «умной» структуре данных введена блокировка, предотвращающая одновременное развитие нескольких потоков.
Я бы порекомендовал профилировать как ваш текущий, минимально-максимальный подход и простое сканирование массива (без связанных списков = меньше памяти), чтобы увидеть, какой из них работает лучше всего. Как ни странно, на практике я видел «умные» алгоритмы со связанными списками, избитые простым сканированием массивов, поскольку более простой подход использует меньше памяти, имеет более плотный цикл и больше выигрывает от оптимизации процессора. Вы также можете избежать проблем с выделением памяти и сборкой мусора с массивом фиксированного размера, содержащим кандидатов.
Один из вариантов, который вы могли бы рассмотреть, какое бы решение ни было, - это сокращение реже и удаление большего количества элементов каждый раз. Например, удаление 100 элементов в каждой операции сокращения означает, что вам нужно сократить только сотую часть времени. Это может позволить более асимметричный подход к добавлению и удалению элементов.
Но в целом, просто имейте в виду, что компьютерный подход к оптимизации не всегда является практическим подходом к достижению максимальной производительности на сегодняшнем и завтрашнем оборудовании.