当前位置: 首页 > article >正文

【算法】Dinner Plate Stacks 餐盘栈

文章目录

  • Dinner Plate Stacks 餐盘栈
    • 问题描述:
    • 分析
    • 代码
  • Tag

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

哈希 设计


http://www.kler.cn/a/16115.html

相关文章:

  • postgresql.conf与postgresql.auto.conf区别
  • Python期末复习 | 列表、元组、字典、集合与字符串 | 代码演示
  • 给阿里云OSS绑定域名并启用SSL
  • Ceph的pool有两种类型
  • 第12章 系统部署
  • 数据产品:深度探索与案例剖析
  • Codeforces Round 867 (Div 3) 总结
  • JavaScript 中 try...catch 的 10 个使用技巧
  • windows10系统如何实现telnet内网穿透
  • 求n个斐波拉契数列的和
  • mysql性能分析
  • Spring Boot 3.x 系列【27】应用篇之集成Lombok简化开发
  • chatgpt 镜像版
  • 【五一创作】[论文笔记]图片人群计数CSRNet,Switch-CNN
  • Linux基础指令
  • 手把手教你爬取网站信息
  • 关于FFMPEG中的filter滤镜的简单介绍
  • Servlet
  • Doris(24):Doris的函数—聚合函数
  • 【2023程序员必看】前端行业分析
  • Vue(简单了解Cookie、生命周期)
  • 《C和指针》笔记2: const关键字
  • 当音乐遇上Python:用Pydub自动分割音频
  • leetcode 198.打家劫舍
  • 国民技术N32G430开发笔记(9)- IAP升级 Bootloader的制作
  • 【云原生|Docker】13-Docker-compose详解