2021.06.22

最大栈

设计要求
我们仔细思考一下 push 和 pop 方法,难点如下:
1、每次 pop 时,必须要知道频率最高的元素是什么。
2、如果频率最高的元素有多个,还得知道哪个是最近 push 进来的元素是哪个。

做法
为了实现上述难点,我们要做到以下几点:
1、肯定要有一个变量 maxFreq 记录当前栈中最高的频率是多少。
2、我们得知道一个频率 freq 对应的元素有哪些,且这些元素要有时间顺序。
3、随着 pop 的调用,每个 val 对应的频率会变化,所以还得维持一个映射记录每个 val 对应的 freq。

综上,我们可以先实现 FreqStack 所需的数据结构:

class FreqStack {
    // 记录 FreqStack 中元素的最大频率
    int maxFreq = 0;
    // 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表
    HashMap<Integer, Integer> valToFreq = new HashMap<>();
    // 记录频率 freq 对应的 val 列表,后文就称为 FV 表
    HashMap<Integer, Stack<Integer>> freqToVals = new HashMap<>();

	// 在栈中加入一个元素 val
    public void push(int val) {}
    
    // 从栈中删除并返回出现频率最高的元素
    // 如果频率最高的元素不止一个,
    // 则返回最近添加的那个元素
    public int pop() {}
}

push():

public void push(int val) {
    // 修改 VF 表:val 对应的 freq 加一
    int freq = valToFreq.getOrDefault(val, 0) + 1;
    valToFreq.put(val, freq);
    // 修改 FV 表:在 freq 对应的列表加上 val
    freqToVals.putIfAbsent(freq, new Stack<>());
    freqToVals.get(freq).push(val);
    // 更新 maxFreq
    maxFreq = Math.max(maxFreq, freq);
}

pop():

public int pop() {
    // 修改 FV 表:pop 出一个 maxFreq 对应的元素 v
    Stack<Integer> vals = freqToVals.get(maxFreq);
    int v = vals.pop();
    // 修改 VF 表:v 对应的 freq 减一
    int freq = valToFreq.get(v) - 1;
    valToFreq.put(v, freq);
    // 更新 maxFreq
    if (vals.isEmpty()) {
        // 如果 maxFreq 对应的元素空了
        maxFreq--;
    }
    return v;
}

求中位数

解法思路
中位数是有序数组最中间的元素算出来的对吧,我们可以把「有序数组」抽象成一个倒三角形,宽度可以视为元素的大小,那么这个倒三角的中部就是计算中位数的元素对吧:
在这里插入图片描述
然后我把这个大的倒三角形从正中间切成两半,变成一个小倒三角和一个梯形,这个小倒三角形相当于一个从小到大的有序数组,这个梯形相当于一个从大到小的有序数组。

中位数就可以通过小倒三角和梯形顶部的元素算出来对吧?嗯,你联想到什么了没有?它们能不能用优先级队列表示?小倒三角不就是个大顶堆嘛,梯形不就是个小顶堆嘛,中位数可以通过它们的堆顶元素算出来。

在这里插入图片描述

梯形虽然是小顶堆,但其中的元素是较大的,我们称其为large,倒三角虽然是大顶堆,但是其中元素较小,我们称其为small。

当然,这两个堆需要算法逻辑正确维护,才能保证堆顶元素是可以算出正确的中位数,我们很容易看出来,两个堆中的元素之差不能超过 1。

因为我们要求中位数嘛,假设元素总数是n,如果n是偶数,我们希望两个堆的元素个数是一样的,这样把两个堆的堆顶元素拿出来求个平均数就是中位数;如果n是奇数,那么我们希望两个堆的元素个数分别是n/2 + 1和n/2,这样元素多的那个堆的堆顶元素就是中位数。

1.根据这个逻辑,我们可以直接写出findMedian函数的代码:

2.不仅要维护large和small的元素个数之差不超过 1,还要维护large堆的堆顶元素要大于等于small堆的堆顶元素,可以写出addNum()方法。

class MedianFinder {
    private PriorityQueue<Integer> small;
    private PriorityQueue<Integer> large;
    /** initialize your data structure here. */
    public MedianFinder() {
         // 小顶堆
        large = new PriorityQueue<>();
        // 大顶堆
        small = new PriorityQueue<>((a, b) -> {
            return b - a;
        });
    }
    
    //  维护了两个顶堆之间的差距为1
    //  维护large堆的堆顶元素大于等于small堆的堆顶元素
    public void addNum(int num) {
        if(small.size() <= large.size()){
            large.offer(num);
            small.offer(large.poll());
        }else{
            small.offer(num);
            large.offer(small.poll());
        }
    }
    
    public double findMedian() {
        // 如果元素不一样多,多的那个堆的堆顶元素就是中位数
        if (large.size() < small.size()) {
            return small.peek();
        } else if (large.size() > small.size()) {
            return large.peek();
        }
        // 如果元素一样多,两个堆堆顶元素的平均数是中位数
        return (large.peek() + small.peek()) / 2.0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值