大顶堆,小顶堆——排序问题

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:

输入:
[“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof

思路:

由于第一次接触这种题,第一想起来的就是初始化一个数组,当需要计算中位数时,我们就把数组排序,之后计算中位数,但是这样太慢,数据量大了之后,就超时。

在这里插入图片描述

既然也都说超时了,我也就不知廉耻的把代码也贴出来吧。

type MedianFinder struct {
    slow []int //切片
    last int //指向下一个可以放数据的索引
}


/** initialize your data structure here. */
func Constructor() MedianFinder {
    return MedianFinder{slow : make([]int , 5e4 + 1) , last : 0}
}


func (this *MedianFinder) AddNum(num int)  {
    this.slow[this.last] = num
    this.last ++
}


func (this *MedianFinder) FindMedian() float64 {
    sort.Ints(this.slow[:this.last])
    if this.last % 2 != 0 {
        return float64(this.slow[this.last / 2])
    } else {
        return float64(this.slow[(this.last - 1) / 2] + this.slow[this.last / 2]) / float64(2)
    }
}


/**
 * Your MedianFinder object will be instantiated and called as such:
 * obj := Constructor();
 * obj.AddNum(num);
 * param_2 := obj.FindMedian();
 */

这种超时的原因一部分在于,一旦要是得到中位数,我们就要排序。

大顶堆、小顶堆

大顶堆:堆顶的数要大于等于它子树的所有数。
小顶堆:堆顶的数要小于等于它子树的所有数。

这道题我们可以将比作一个薯片桶,我们将大顶堆放到左侧,小顶堆放到右侧,也就是说我们新添加的数要根据添加的数大小判断放到大顶堆还是小顶堆,还要根据大顶堆和小顶堆的长度判断放到大顶堆还是小顶堆。

  1. 我们先要判断大顶堆和小顶堆的长度,我们让大顶堆和小顶堆的长度尽可能的相等,只有这样,我们计算中位数时,就可以直接找到中间的数。
  2. 我们不仅要判断大顶堆和小顶堆的长度,我们还要判断加入的数和大顶堆堆顶的数、小堆顶的堆顶的数的相对大小,如果我们要加入一个数,如果加入之前大顶堆长度小于小顶堆的长度,我们既然要让两个堆长度尽量相等,我们就要将这个数放入大顶堆,但是这时问题出现了,如果放入这个数,比大顶堆的堆顶的数还要大,那我们就不能放入大顶堆,只能放入小顶堆,但是如果这个数放入小顶堆,那么小顶堆就比大顶堆的长度长两个长度。所以我们将该数放入小顶堆之后,也要将小顶堆的数放入到大顶堆中。
type maxheap []int

type minheap []int

func (m *maxheap) Push (num interface{}) {
    *m = append(*m , num.(int))
}

func (m *minheap) Push (num interface{}) {
    *m = append(*m , num.(int))
}

func (m *maxheap) Pop () interface{} {
    length := len(*m)
    num := (*m)[length - 1]
    *m = (*m)[:length - 1]
    return num
}

func (m *maxheap) Swap(i , j int) {
    (*m)[i] , (*m)[j] = (*m)[j] , (*m)[i]
}

func (m *minheap) Swap(i , j int) {
    (*m)[i] , (*m)[j] = (*m)[j] , (*m)[i]
}

func (m maxheap) Len() int {
    return len(m)
}

func (m minheap) Len() int {
    return len(m)
}

func (m maxheap) Less (i , j int) bool {
    return m[i] > m[j]
}

func (m minheap) Less (i , j int) bool {
    return m[i] < m[j]
}

func (m *minheap) Pop () interface{} {
    length := len(*m)
    num := (*m)[length - 1]
    *m = (*m)[:length - 1]
    return num
}
type MedianFinder struct {
    maxh *maxheap
    minh *minheap
}


/** initialize your data structure here. */
func Constructor() MedianFinder {
    return MedianFinder{maxh : new(maxheap) , minh : new(minheap)}
}


func (this *MedianFinder) AddNum(num int)  {
    if this.maxh.Len() == this.minh.Len() {
        if this.minh.Len() == 0 || (*this.minh)[0] <= num {
            heap.Push(this.minh , num)
        } else {
            heap.Push(this.maxh , num)
            n := heap.Pop(this.maxh).(int)
            heap.Push(this.minh , n)
        }
    } else {
        if num > (*this.minh)[0] {
            heap.Push(this.minh , num)
            n := heap.Pop(this.minh)
            heap.Push(this.maxh , n.(int))
        } else {
            heap.Push(this.maxh , num)
        }
    }
}


func (this *MedianFinder) FindMedian() float64 {
    if this.minh.Len() == this.maxh.Len() {
        return float64((*this.minh)[0] + (*this.maxh)[0]) / 2.0
    } else {
        return float64((*this.minh)[0])
    }
}


/**
 * Your MedianFinder object will be instantiated and called as such:
 * obj := Constructor();
 * obj.AddNum(num);
 * param_2 := obj.FindMedian();
 */

一共分为四种可能:

一个数应该加入到大顶堆

如果这个数大于大顶堆的堆顶那么这个数先放入小顶堆,然后再将小顶堆的堆顶放入大顶堆。
一个数应该加入到小顶堆
如果这个数小于小顶堆的堆顶,那么这个数先放入大顶堆,然后将大顶堆的堆顶放入小顶堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值