Кучи
K-й наибольший в потоке
Поддерживать k наибольших элементов потока
Постановка задачи
Описание (LeetCode - 703. Kth Largest Element in a Stream)
Вы работаете в приемной комиссии университета и должны в режиме реального
времени отслеживать k-й наивысший балл по результатам тестов абитуриентов. Это
помогает динамически определять проходные баллы для собеседований и зачисления
по мере того, как новые абитуриенты предоставляют свои результаты.
Вам нужно реализовать класс, который для заданного целого числа k поддерживает
поток результатов тестов и непрерывно возвращает k-й наивысший результат теста
после отправки нового результата.
Класс поддерживает две операции:
KthLargest(k, nums)- создает объект и инициализирует его числами изnums.add(val)- добавляет новое числоvalв поток и возвращаетk-й наибольший элемент среди всех полученных чисел.
Пример 1:
k = 3
nums = [4, 5, 8, 2]
Операции:
add(3) -> 4
add(5) -> 5
add(10) -> 5
add(9) -> 8
add(4) -> 8
Пояснение:
После add(3) поток содержит [4, 5, 8, 2, 3]. Три наибольших числа - 8, 5
и 4, поэтому третий наибольший элемент равен 4.
После add(5) три наибольших числа - 8, 5 и 5, поэтому ответ равен 5.
Ограничения:
1 <= k <= 10⁴0 <= nums.length <= 10⁴-10⁴ <= nums[i] <= 10⁴-10⁴ <= val <= 10⁴- Будет выполнено не более
10⁴вызововadd.
Решение
Нам не нужно хранить весь поток в отсортированном виде. После каждого добавления
достаточно знать только k наибольших элементов. Самый маленький среди этих k
элементов и будет ответом.
Идея решения:
Будем поддерживать минимальную кучу размера не более k. В этой куче хранятся
текущие k наибольших элементов потока.
Минимальная куча нужна для того, чтобы быстро находить наименьший из k
наибольших элементов. Именно он является k-м наибольшим элементом.
Если в кучу попадает больше k элементов, мы удаляем минимум. Так мы убираем
самый маленький элемент из кандидатов и оставляем только k наибольших.
Основные шаги:
- Создаем минимальную кучу.
- Добавляем в нее все числа из
numsс помощью методаadd. - В
add(val)кладемvalв кучу. - Если размер кучи превышает
k, удаляем минимум. - Возвращаем вершину кучи.
Рассмотрим пример:
k = 3
nums = [4, 5, 8, 2]
- После инициализации куча хранит три наибольших элемента:
[4, 5, 8]. Минимум среди них равен4, значит, текущий третий наибольший элемент -4.
- Добавим
3. В куче временно окажутся[3, 4, 5, 8].
Размер кучи превысил k, поэтому удаляем 3. В куче остаются [4, 5, 8],
ответ равен 4.
- Добавим
5. В куче временно окажутся[4, 5, 5, 8].
Удаляем 4. Теперь минимум среди трех лучших равен 5, поэтому ответ равен
5.
Важно не путать эту задачу с поиском k-го наибольшего элемента в статическом
массиве. Здесь числа приходят постепенно, поэтому после каждого add нужно
быстро обновлять ответ.
Оценка сложности
Временная сложность
Добавление одного элемента в кучу размера k занимает O(log k).
Инициализация массивом из n элементов занимает O(n log k), а каждый
последующий вызов add выполняется за O(log k).
Пространственная сложность
Куча хранит не более k элементов, поэтому пространственная сложность равна
O(k).
Код решения
Приведем код решения.
import "github.com/emirpasic/gods/trees/binaryheap"
type KthLargest struct {
k int
heap *binaryheap.Heap
}
func Constructor(k int, nums []int) KthLargest {
kth := KthLargest{k: k, heap: binaryheap.NewWithIntComparator()}
for _, num := range nums {
kth.Add(num)
}
return kth
}
func (this *KthLargest) Add(val int) int {
this.heap.Push(val)
if this.heap.Size() > this.k {
this.heap.Pop()
}
peek, _ := this.heap.Peek()
return peek.(int)
}Итоги
Задача про k-й наибольший элемент в потоке показывает, как куча помогает
работать с данными, которые приходят постепенно.
- Суть алгоритма: мы поддерживаем минимальную кучу размера не более
k, в которой хранятсяkнаибольших элементов. - Эффективность:
- Временная сложность:
O(n log k)для инициализации иO(log k)для каждого вызоваadd. - Пространственная сложность:
O(k), так как мы храним в куче не болееkэлементов.
- Временная сложность:
- Граничные случаи:
- Массив
numsможет быть пустым: элементы заполнят кучу по мере вызововadd. - Повторяющиеся числа считаются отдельными элементами, поэтому два одинаковых
значения могут оба входить в
kнаибольших.
- Массив
- Оптимальное решение: нам не нужно хранить весь поток в отсортированном
виде. Достаточно поддерживать
kнаибольших элементов и быстро получать наименьший из них.