Как узнать
k самых больших, малых, близких или частых элементов либо k-й из них, часто из длинного списка или потока.
Как решать такие задачи
- Переверни кучу: для k самых больших бери мин-кучу, для k самых малых — макс-кучу.
- Выбери ключ для сравнения: значение, счётчик из словаря, расстояние до точки.
- Заполни кучу до k элементов. Дальше новый элемент заменяет корень, только если он лучше корня.
- В конце в куче лежит ответ, а в её корне — k-й лучший элемент.
Вариант: Слияние k списков
То же, но в куче лежат первые элементы k отсортированных списков. Наверху всегда следующий элемент общего ответа, поэтому не нужно каждый раз сравнивать все k списков.
- Положи в кучу по одному кандидату от каждого списка — его первый узел, пустые списки пропусти. Куча сравнивает узлы по node.val.
- Достань наименьший узел и добавь его в ответ.
- Если у него есть следующий, положи node.next в кучу: теперь он кандидат от этого списка.
- Так же решаются k-й наименьший в матрице с отсортированными строками (достать k раз) и наименьший диапазон, покрывающий все списки (ещё следи за наибольшим кандидатом).
Вариант: Две кучи
То же, но куч две: одна держит меньшую половину чисел, другая — большую. Медиана всегда лежит наверху одной из них, и после каждого нового числа не нужно сортировать всё заново.
- Как узнать: числа приходят постепенно, а середина нужна снова и снова, или вообще нужно делить числа на нижнюю и верхнюю части.
- Держи две кучи: макс-кучу low для меньшей половины и мин-кучу high для большей.
- Каждое число клади в low, потом переноси верх low в high. Так всё в low остаётся не больше всего в high.
- Если high стала больше low, верни её верх обратно. Теперь в low столько же чисел или на одно больше.
- Ответ бери с верхов: при нечётном количестве — верх low, при чётном — среднее двух верхов.
Где встречается
- Таймеры в Node.js. Библиотека libuv, на которой работает Node.js, хранит таймеры в куче: наверху всегда тот, что сработает раньше всех. Поэтому, чтобы понять, сколько ждать, ей не нужно перебирать все таймеры.
- Python. heapq.nlargest(k, данные) держит кучу из k лучших и пропускает через неё остальные данные, а не сортирует всё.
- RocksDB. RocksDB хранит данные в нескольких отсортированных файлах. Чтобы читать их по порядку, он сливает их через кучу, где наверху всегда следующий ключ.