支持删除任意元素的堆
支持删除任意元素的堆
堆可以在常数时间获知最值(即堆顶元素),对数时间插入元素、删除堆顶元素。
但有时候需要能迅速删除任意一个元素,比如 480. 滑动窗口中位数 这样的问题。
有没有办法让删除操作也在对数复杂度呢?
标准库提供了 Remove 方法,这个方法本身是对数级复杂度的,不过方法接受的是元素在堆里的索引,而不是元素本身(类似地还有个 Fix 方法)。要能迅速获知元素在堆里的索引,需要额外的空间做记录。详细可参考标准库优先队列相关示例。
下边介绍一个延迟删除的技巧。
延迟删除
在要删除一个元素时,先不急着从堆里真正删除,而是先把它记录下来。后边因各种操作如果待删除元素到达了堆顶,就可以在对数时间内把它真正从堆里删除了。
可以用一个哈希表或者另一个堆来存储所有待删除的元素。下边写一个使用哈希表存储待删除元素的实现,简单起见,假设堆里存储的都是 int 类型的元素:
type Heap struct {
slice []int
cmp func(int, int) bool
// 缓存应该删除的元素,键为元素,值为应该删除的个数
delMemo map[int]int
// 因为待删除元素缓存,用一个额外的属性维护堆的真实大小
size int
}
func (h *Heap) Len() int {return len(h.slice)}
func (h *Heap) Less(i, j int) bool {return h.cmp(h.slice[i], h.slice[j])}
func (h *Heap) Swap(i, j int) {h.slice[i], h.slice[j] = h.slice[j], h.slice[i]}
func (h *Heap) Push(x interface{}) {h.slice = append(h.slice, x.(int))}
func (h *Heap) Pop() interface{} {
n := len(h.slice)
res := h.slice[n-1]
h.slice = h.slice[:n-1]
return res
}
// push 向堆里添加一个元素
func (h *Heap) push(x int) {
h.clean()
h.size++
heap.Push(h, x)
}
// pop 删除堆顶元素并返回其值
func (h *Heap) pop() int {
h.clean()
h.size--
return heap.Pop(h).(int)
}
// peek 获取堆顶元素
func (h *Heap) peek() int {
h.clean()
return h.slice[0]
}
// remove 在堆里删除元素 num
func (h *Heap) remove(x int) {
h.clean()
h.size--
h.delMemo[x]++
}
// 循环检查堆顶元素,如果在 delMemo 缓存中则删除
func (h *Heap) clean() {
for len(h.slice) > 0 && h.delMemo[h.slice[0]] > 0 {
h.delMemo[h.slice[0]]--
heap.Pop(h)
}
}附480.滑动窗口中位数的参考解答:
func medianSlidingWindow(nums []int, k int) []float64 {
if k > len(nums) {
k = len(nums)
}
if k < 1 {
return nil
}
mf := NewMedianFinder()
for _, v := range nums[:k-1] {
mf.AddNum(v)
}
res := make([]float64, 0, len(nums)-k+1)
for i := k-1; i < len(nums); i++ {
mf.AddNum(nums[i])
res = append(res, mf.FindMedian())
mf.Remove(nums[i-k+1])
}
return res
}
type MedianFinder struct {
left, right *Heap
}
func NewMedianFinder() *MedianFinder {
return &MedianFinder{
left: &Heap{cmp: func(x, y int) bool {return x > y}, delMemo: map[int]int{}},
right: &Heap{cmp: func(x, y int) bool {return x < y}, delMemo: map[int]int{}},
}
}
func (mf *MedianFinder) AddNum(num int) {
if mf.left.size == 0 || mf.left.peek() >= num {
mf.left.push(num)
} else {
mf.right.push(num)
}
mf.makeBalance()
}
func (mf *MedianFinder) makeBalance() {
if mf.left.size > mf.right.size+1 {
mf.right.push(mf.left.pop())
} else if mf.left.size < mf.right.size {
mf.left.push(mf.right.pop())
}
}
func (mf *MedianFinder) FindMedian() float64 {
if mf.left.size > mf.right.size {
return float64(mf.left.peek())
}
return float64(mf.left.peek()+mf.right.peek())/2
}
func (mf *MedianFinder) Remove(x int) {
if mf.left.size > 0 && mf.left.peek() >= x {
mf.left.remove(x)
} else {
mf.right.remove(x)
}
mf.makeBalance()
}