【算法】Dinner Plate Stacks 餐盘栈

Dinner Plate Stacks 餐盘栈

问题描述:

有一个无限长度的行,从左到右每隔一段就有一个栈,容量是capacity ,编号从0开始。
要求实现一个餐盘的类。
初始化 DinnerPlates(int capacity) - 给出栈的最大容量 capacity
void push(int val) - 将给出的正整数 val 推入 从左往右第一个 没有满的栈。
int pop() - 返回 从右往左第一个 非空栈顶部的值,并将其从栈中删除;如果所有的栈都是空的,请返回 -1。
int popAtStack(int index) - 返回编号 index 的栈顶部的值,并将其从栈中删除;如果编号 index 的栈是空的,请返回 -1。
capacity的范围[1,20000],val的范围[1,20000]
index 的范围[0,100000],最多会对push,pop,popAtStack进行200000次的调用。

分析

首先是暴力的思路,可以初始化一个长度是100000+的数组,数组的每个元素是一个栈。
如果不考虑popAtStack,那么每次push和pop都应该很接近,即push的位置左侧应该都是满的,pop的右侧应该都是空的。
但是因为popAtStack的存在,会使得整个数组可能呈现锯齿状。
为了寻找左起第一个不满的pushind,就需要从0开始遍历,时间复杂度ON。同样的为了找到右起第一个不空的popind,就需要从末尾开始向左遍历,时间复杂度ON.
每次pop,push的时间复杂度ON,而且调用次数是ON,整体时间复杂度ON*N。
而且为了放下所有的栈,开一个100000大小的数组。无论从空间还是时间的角度,在算法界也是相当炸裂的。
对于push来说,它需要关心的是不满的位置。而pop,关心的是不空的。
也就是说push 关心 下标最小的&& 0<=size<cap,pop ,关心 下标最大的&& 0<size<=cap.
如果可以找到快速维护这样特性的数据结构,就可以解决问题。
使用小顶堆存放 每个不满的栈的ind,同时可以使用list存放栈,list的长度就表示了下标最大的栈位置。

push操作时,首先要判断小顶堆是否empty,或者当前的下标,是否超过了目前list的长度。如果超过了,就要把小顶堆清空,这里涉及到一个lazy del。
再判断如果小顶堆空了,说明此时没有不满的栈,就必须新建一个栈插入list尾部,同时将数据入栈,并且判断栈是否不满,条件满足的情况下入小顶堆。
如果小顶堆不空,那么就使用大顶堆当前的栈,将元素入栈,同时判断栈是否满了,如果满了,就出堆。

pop的操作,是弹出最大下标的栈顶。而popAtstatck是弹出指定下标的栈顶,所以2个可以合并。
pop== popAtStack(last),只要构建一个popAtStack(idx)就可以。
因为关心的是都是非空栈,所以需要进行维护list,是list的末尾始终是非空的。
先判断idx的范围,idx的范围必须满足[0,list.size()-1].
idx合法,再判断栈是否空。
此时idx对应的栈一定是非空的,将元素弹出,同时要将该栈入小顶堆。
最后对list维护,如果list的尾部栈空了,就从list尾部删除。

整体的时间复杂度ONLogN,空间ON
设计的问题,就是非常的烧。
时间复杂度 O(N(logN))
空间复杂度: O(N)

代码

时间复杂度 O(N(logN))
空间复杂度: O(N)

class DinnerPlates {
    // 栈的容量
    private int capacity;
    // 所有栈
    private List<Deque<Integer>> stacks = new ArrayList<>();
    // 最小堆,保存未满栈的下标
    private PriorityQueue<Integer> idx = new PriorityQueue<>();

    public DinnerPlates(int capacity) {
        this.capacity = capacity;
    }

    public void push(int val) {
        if (!idx.isEmpty() && idx.peek() >= stacks.size())
            idx.clear(); // 堆中都是越界下标,直接清空
        if (idx.isEmpty()) { // 所有栈都是满的
            var st = new ArrayDeque<Integer>();
            st.push(val);
            stacks.add(st); // 添加一个新的栈
            if (capacity > 1) // 新的栈没有满
                idx.add(stacks.size() - 1); // 入堆
        } else { // 还有未满栈
            var st = stacks.get(idx.peek());
            st.push(val); // 入栈
            if (st.size() == capacity) // 栈满了
                idx.poll(); // 从堆中去掉
        }
    }

    public int pop() {
        // 等价为 popAtStack 最后一个非空栈
        return popAtStack(stacks.size() - 1);
    }

    public int popAtStack(int index) {
        if (index < 0 || index >= stacks.size() || stacks.get(index).isEmpty())
            return -1; // 非法操作
        var st = stacks.get(index);
        if (st.size() == capacity) // 满栈
            idx.add(index); // 元素出栈后,栈就不满了,把下标入堆
        int val = st.pop();
        // 去掉末尾的空栈(懒删除,堆中下标在 push 时处理)
        while (!stacks.isEmpty() && stacks.get(stacks.size() - 1).isEmpty())
            stacks.remove(stacks.size() - 1);
        return val;
    }
}

代码来源于 灵神大佬,非常优雅,值得学习

Tag

哈希 设计

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eric.Cui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值