设计:栈和队列相互实现、最小栈、增量更新栈

栈实现队列

栈实现队列
栈和队列共同的特点就是支持基于头或尾的操作,其中队列支持“一侧进,另一侧出”,而栈仅能对一侧进行进出,即栈无法直接删除栈底节点
而此时我们需要一个另外一个辅助栈,让这个辅助栈存储当前栈的倒序序列,那么栈顶不就是辅助栈的栈顶元素了吗

    LinkedList<Integer> stack1 = new LinkedList<>();
    LinkedList<Integer> stack2 = new LinkedList<>();

其中stack1存放正序序列,而stack2在用于存放倒序序列(仅在删除时作为一个辅助栈去使用即可)

public void appendTail(int value) {
        stack1.push(value);
    }

元素往1放入,每次delete就往2里面导入,寻找的时候先往2中找(因为2充当的实际上是一个队列首部的角色,而1充当的是一个队列尾部的角色

public int deleteHead() {
        if(!stack2.isEmpty()){
            return stack2.pop();    //stack2的栈顶就是队列的头
        }
        if(stack1.isEmpty()){
            return -1;
        }
        while(!stack1.isEmpty()){
            stack2.push(stack1.pop());
        }
        return stack2.pop();
    }

元素不存在冗余的部分,要么在stack1,要么在stack2,因此删除时先从2中(逻辑上的队头一侧)寻找,否则将1中元素全部导入2后再取出队首元素

队列实现栈

队列实现栈
假如现在由4个元素 1 2 3 4依次进入队列和栈,那么对应[1,2,3,4],其中队列出队的顺序是1 2 3 4,而栈则是 4 3 2 1
因此可以看出来,队列和栈在出队的时候,序列是反的,那么可不可以在将元素入队的时候就执行一个反向操作

    Queue<Integer> queue=new LinkedList<>();

我们使用一个队列去实现栈,每次元素入栈的时候,我们将之前的元素依次弹出,并且再次加入队中

    public void push(int x) {
        queue.offer(x);
        for (int i = 0; i < queue.size() - 1; i++) {//刚加入的x不进行操作
            queue.add(queue.poll());//一边弹一边加入
        }
    }

我们依次加入1 2 3 4,那么第一次加入[1],然后是[2,1],接着[3,2,1],最后的队列为[4,3,2,1]。其实就是相当于每次进行头插来进行一个反序操作,但是根据队列的特性我们只能进行头删和尾插,因为我们通过将队列现有元素依次弹出再插入当新元素的后面,模拟一个新元素头插的操作
因为我们每次push都可以保证“即使底层数据结构是队列,元素顺序依然对应栈的顺序”,因此其他操作都是常规的删除和查看。

public int pop() {
        return queue.poll();
    }
public int top() {
        return queue.peek();
    }
public boolean empty() {
        return queue.peek() == null ? true : false;
    }

最小栈

最小栈
为了能够在O(1)的时间复杂度拿到栈中最小值,我们需要使用一个专门的数据结构去维护栈中元素的最小值。

    LinkedList<Integer> min=new LinkedList<>();
    LinkedList<Integer> arr=new LinkedList<>();

我们可以维护一个递减的单调栈,例如-1 3 -2 0 -3 8,我们使用一个常规的栈维护元素[-1,3,-2,0,-3,8],而最小栈维护一个栈顶保存最小值的结构[-1,-2,-3]

public void push(int val) {
        if(min.isEmpty()||min.peek()>=val){
            min.push(val);
        }
        arr.push(val);
    }

最小值总是单调的,栈顶元素保存当前栈的最小值,并且可以直接在O(1)拿到最小值

    public int getMin() {
        return min.peek();
    }

如果当前删除的值就是最小值的栈顶元素,那么此时的最小值也需要被更新。

    public void pop() {
        Integer p = arr.pop();
        if(p.equals(min.peek())){
            min.pop();
        }
    }

支持增量更新的栈

设计一个支持增量更新的栈
增量更新就是渐进式的更新,更新操作不是一次完成的。例如redis的哈希表扩容就是基于渐进式的,一旦发生扩容行为,增删改查方法都会同时在两个哈希表中进行,并且捎带进行扩容直到扩容完毕。
以inc方法为例:
void inc(int k, int val):栈底的 k 个元素的值都增加 val 。如果栈中元素总数小于 k ,则栈中的所有元素都增加 val 。
我们的最终目标是将inc方法的时间复杂度进行O(1)实现。

    int maxSize=0;
    int[] stack;
    int index=0;	
    //记录增量更新
    int[] add;

	public CustomStack(int maxSize) {
        this.maxSize=maxSize;
        stack = new int[maxSize];
        add = new int[maxSize];
    }

arr[index++]=val,index指向待插入的位置

我们使用一个辅助数组add记录每次inc()的增量,如果inc操作将栈底K个元素增加val,那么我们就在第k-1的位置记录这个增量val(下标从0开始)。基于差分思想当我们需要获取这个值的时候,只需从add数组中获取增量并更新数组中的值即可

    public void increment(int k, int val) {
        int limit = Math.min(k-1,index-1);
        if(limit>0){
            add[limit]+=val;
        }
    }

仅存储增量,不对数组中的元素进行实际的更新操作。

    public void push(int x) {
        if(isFull())return;
        stack[index++]=x;
    }

push的时候,不涉及获取元素的值,因此不需要将增量更新到对应元素。

    public int pop() {
        if(isEmpty()) return -1;
        //仅在pop时才获取具体值
        index--;
        //将增量累加到元素上
        int res = stack[index]+add[index];
        if(index-1>=0){
        	//增量更新
            add[index-1]+=add[index];
        }
        add[index]=0;//增量复原
        return res;
    }

因为pop()调用,我们需要返回元素的值,这个时候将增量更新到元素上,并且将对应位置的增量重置为0
注意:从栈顶到栈底方向,增量是叠加的,而这个叠加也是“懒更新”的,例如add数组记录[0,1,3,0,0],意味着栈底的三个元素需要最终被累加:1+3,1+3和3 。并且一旦更新完毕增量就可以复原为0(累加任务进行完毕了,清除任务列表)

    private boolean isEmpty(){
        return index==0;
    }
    private boolean isFull(){
        return index==maxSize;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值