LC-1172. 餐盘栈(最小堆(懒删除堆))

文章介绍了如何使用优先队列作为数据结构来优化‘餐盘’类的实现,以提高push、pop和popAtStack操作的效率。通过维护一个最小堆来存储未满栈的下标,可以快速找到第一个未满的栈,同时利用懒删除堆的概念减少不必要的堆操作,解决了暴力方法的效率问题。
摘要由CSDN通过智能技术生成

1172. 餐盘栈

难度困难48

我们把无限数量 ∞ 的栈排成一行,按从左到右的次序从 0 开始编号。每个栈的的最大容量 capacity 都相同。

实现一个叫「餐盘」的类 DinnerPlates

  • DinnerPlates(int capacity) - 给出栈的最大容量 capacity
  • void push(int val) - 将给出的正整数 val 推入 从左往右第一个 没有满的栈。
  • int pop() - 返回 从右往左第一个 非空栈顶部的值,并将其从栈中删除;如果所有的栈都是空的,请返回 -1
  • int popAtStack(int index) - 返回编号 index 的栈顶部的值,并将其从栈中删除;如果编号 index 的栈是空的,请返回 -1

示例:

输入: 
["DinnerPlates","push","push","push","push","push","popAtStack","push","push","popAtStack","popAtStack","pop","pop","pop","pop","pop"]
[[2],[1],[2],[3],[4],[5],[0],[20],[21],[0],[2],[],[],[],[],[]]
输出:
[null,null,null,null,null,null,2,null,null,20,21,5,4,3,1,-1]

解释:
DinnerPlates D = DinnerPlates(2);  // 初始化,栈最大容量 capacity = 2
D.push(1);
D.push(2);
D.push(3);
D.push(4);
D.push(5);         // 栈的现状为:    2  4
                                    1  3  5
                                    ﹈ ﹈ ﹈
D.popAtStack(0);   // 返回 2。栈的现状为:      4
                                          1  3  5
                                          ﹈ ﹈ ﹈
D.push(20);        // 栈的现状为:  20  4
                                   1  3  5
                                   ﹈ ﹈ ﹈
D.push(21);        // 栈的现状为:  20  4 21
                                   1  3  5
                                   ﹈ ﹈ ﹈
D.popAtStack(0);   // 返回 20。栈的现状为:       4 21
                                            1  3  5
                                            ﹈ ﹈ ﹈
D.popAtStack(2);   // 返回 21。栈的现状为:       4
                                            1  3  5
                                            ﹈ ﹈ ﹈ 
D.pop()            // 返回 5。栈的现状为:        4
                                            1  3 
                                            ﹈ ﹈  
D.pop()            // 返回 4。栈的现状为:    1  3 
                                           ﹈ ﹈   
D.pop()            // 返回 3。栈的现状为:    1 
                                           ﹈   
D.pop()            // 返回 1。现在没有栈。
D.pop()            // 返回 -1。仍然没有栈。

提示:

  • 1 <= capacity <= 20000
  • 1 <= val <= 20000
  • 0 <= index <= 100000
  • 最多会对 pushpop,和 popAtStack 进行 200000 次调用。

超时,暴力做法

暴力做法的缺点,每次push和pop时都从端点开始找栈,有没有数据结构能帮我维护未满的栈

class DinnerPlates {

    List<Deque<Integer>> list;
    int capacity;

    public DinnerPlates(int capacity) {
        list = new ArrayList<>();
        this.capacity = capacity;
    }
    
    public void push(int val) {
        int idx = 0;
        while(idx < list.size()){
            if(list.get(idx).size() < capacity) break;
            idx++;
        }
        if(idx == list.size()) list.add(new ArrayDeque<>());
        Deque<Integer> cur = list.get(idx);
        cur.push(val);
        list.set(idx, cur);
    }
    
    public int pop() {
        int idx = list.size() - 1;
        while(idx > 0){
            if(list.get(idx).size() > 0) break;
            idx--;
        }
        if(idx == 0 && list.get(0).size() == 0) return -1; 
        Deque<Integer> cur = list.get(idx);
        int res = cur.poll();
        list.set(idx, cur);
        return res;
    }
    
    public int popAtStack(int idx) {
        if(idx > list.size()) return -1;
        Deque<Integer> cur = list.get(idx);
        if(cur.size() == 0) return -1;
        int res = cur.poll();
        list.set(idx, cur);
        return res;
    }
}

懒删除堆:

题解:https://leetcode.cn/problems/dinner-plate-stacks/solution/yu-qi-wei-hu-di-yi-ge-wei-man-zhan-bu-ru-sphs/

假如一开始连续执行了很多次 push 操作,就会得到一排整整齐齐的栈;然后再执行一些 popAtStack 操作,随机挑选 index,就会把这些栈弄得参差不齐。

这个时候再交替执行 push 和 popAtStack,「从左往右第一个没有满的栈」就没有什么规律可言了。如果把第一个未满栈填满,就要暴力寻找下一个未满栈了。

如何优化?

格局打开:与其维护第一个未满栈,不如维护所有未满栈。

假设可以用一个数据结构来维护这些未满栈(的下标),看看需要哪些操作:

  • **对于 push 来说,需要知道这些下标中的最小值。**如果入栈后,栈满了,那么这个栈就不能算作未满栈,此时这个最小下标就需要从数据结构中移除

  • 对于 popAtsStack 来说,如果操作的是一个满栈,操作后就得到了一个未满栈,那么就往数据结构中添加这个栈的下标

  • 对于 pop 来说,它等价于 popAtStack(lastIndex),其中lastIndex 是最后一个非空栈的下标

所以我们需要一个支持添加元素、查询最小值和移除最小值的数据结构。最小堆,就你了。

此外还需要一个列表 stacks,它的每个元素都是一个栈。上面说的lastIndex 就是 stacks 的长度减一。

如何维护 stacks 呢?

  • 如果 push 时最小堆为空,说明所有栈都是满的,那么就需要向 stacks 的末尾添加一个新的栈。如果 capacity >1,就把这个新的未满栈的下标入堆

  • 如果 popAtstack 操作的是最后一个栈,且操作后栈为空,那么就从 stacks 中移除最后一个栈。如果移除后 stacks 的最后一个栈也是空的,就不断移除直到 stacks 为空或者最后一个栈不为空。细节: 需要同步移除最小堆中的下标吗? 其实不需要如果在 push 时发现堆顶的下标大于等于 stacks 的长度,把整个堆清空即可。 (这个技巧叫懒删除。)

  • 此外,如果 popAtstack 操作越界或者操作的栈为空,则返回 -1

问: 懒删除是否会导致堆中有重复的下标?

答: 不会有重复下标。

假设重复下标是在 push 时插入的,说明此前所有栈都是满的,堆中残留的下标必然都大于等于 stacks 的长度但这种情况下 push 会清空堆,不会导致重复下标。

假设重复下标是在 popAtstack 时插入的,这只会发生在 tackslindet 是满栈的情况下,而 push 保证在满栈时会去掉这个下标,所以也不会导致重复下标。

class DinnerPlates {
	// 懒删除堆:在pop元素时,该栈中 size=0 的时候不同步进行移除最小堆中的下标,只移除stacks列表中末尾的空栈
    // 		   在push时判断堆顶下标是否越界了(idx.peek() >= stacks.size()),越界则删除

    // 栈的容量
    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()){ // 所有的栈都是满的
            Deque<Integer> st = new ArrayDeque<>();
            st.push(val);
            stacks.add(st); // 添加一个新的栈
            if(capacity > 1){ // 新的栈没有满
                idx.add(stacks.size() - 1); // 入堆
            }
        }else{ // 还有未满栈,在最小下标对应的栈中添加元素
            Deque<Integer> 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; // 非法操作
        Deque<Integer> 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;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Sure! Here's your code with comments added: ```matlab F = zeros(length(z), 1); % Initialize the F vector with zeros for i = 1:length(z) % Define the Phi function using anonymous function Phi = @(theta, R, r) (z(i) + lc - lm) .* r.R .(R - r.sin(theta)) ./ ... ((R.^2 + r.^2 - 2*R.*r.*sin(theta)).sqrt(R.^2 + r.^2 + (z(i) + lc - lm).^2 - 2*R.*r.*sin(theta))) + ... (z(i) - lc + lm) .* r.R .(R - r.sin(theta)) ./ ... ((R.^2 + r.^2 - 2*R.*r.*sin(theta)).sqrt(R.^2 + r.^2 + (z(i) - lc + lm).^2 - 2*R.*r.*sin(theta))) + ... (z(i) + lc + lm) .* r.R .(R - r.sin(theta)) ./ ... ((R.^2 + r.^2 - 2*R.*r.*sin(theta)).sqrt(R.^2 + r.^2 + (z(i) + lc + lm).^2 - 2*R.*r.*sin(theta))) + ... (z(i) - lc - lm) .* r.R .(R - r.sin(theta)) ./ ... ((R.^2 + r.^2 - 2*R.*r.sin(theta)).sqrt(R.^2 + r.^2 + (z(i) - lc - lm).^2 - 2*R.*r.sin(theta))); % Calculate the value of F(i) using the integral3 function F(i) = BrNI / (4 * lc * (Rc - rc)) * integral3(Phi, 0, 2*pi, rc, Rc, rm, Rm); end ``` This code calculates the values of the vector `F` using a loop. The `Phi` function is defined as an anonymous function that takes `theta`, `R`, and `r` as input parameters. It performs a series of calculations and returns a value. The integral of `Phi` is then calculated using the `integral3` function. The result is stored in the corresponding element of the `F` vector. Please note that I have made some assumptions about the variables and functions used in your code since I don't have the complete context. Feel free to modify or clarify anything as needed.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值