使用队列实现栈

题目介绍

力扣i225题:https://leetcode-cn.com/problems/implement-stack-using-queues/
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:
在这里插入图片描述
进阶:你能否实现每种操作的均摊时间复杂度为 O(1) 的栈?换句话说,执行 n 个操作的总时间复杂度 O(n) ,尽管其中某个操作可能需要比其他操作更长的时间。你可以使用两个以上的队列。

分析

这道题目涉及到栈和队列两种数据结构。它们的共同特点是,数据元素以线性序列的方式存储;区别在于,元素进出的方式不同。

队列本身对数据元素的保存,是完全符合数据到来次序的,同时也保持这个顺序依次出队。而弹栈操作的实现,是要删除最后进入的数据,相当于反序弹出。

实现的基本思路是,我们可以用一个队列保存当前所有的数据,以它作为栈的物理基础;而为了保证后进先出,我们在数据入队之后,就把它直接移动到队首。

方法一:两个队列实现

可以增加一个队列来做辅助。我们记原始负责存储数据的队列为queue1,新增的辅助队列为queue2。

  • 当一个数据x压栈时,我们不是直接让它进入queue1,而是先在queue2做一个缓存。默认queue2中本没有数据,所以当前元素一定在队首。

    queue1:a b
    queue2:x

  • 接下来,就让queue1执行出队操作,把之前的数据依次输出,同时全部添加到queue2中来。这样,queue2就实现了把新元素添加到队首的目的。

    queue1:
    queue2:x a b

  • 最后,我们将queue2的内容复制给queue1做存储,然后清空queue2。在代码上,这个实现非常简单,只要交换queue1和queue2指向的内容即可。

    queue1:x a b
    queue2:

而对于弹栈操作,只要直接让queue1执行出队操作,删除队首元素就可以了
代码演示如下:

// 使用两个队列实现自定义栈
public class MyStack {
    // 定义两个队列
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    // 入栈方法
    public void push(int x){
        // 1. 把x保存到queue2中
        queue2.offer(x);

        // 2. 将queue1中所有元素依次出队,然后放入queue2
        while (!queue1.isEmpty()){
            queue2.offer( queue1.poll() );
        }

        // 3. 交换两个队列
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }

    // 出栈操作
    public int pop(){
        // queue1出队就是出栈
        return queue1.poll();
    }

    // 获取栈顶元素
    public int top(){
        return queue1.peek();
    }

    // 判断为空
    public boolean empty(){
        return queue1.isEmpty();
    }
}

复杂度分析

  • 时间复杂度:入栈操作 O(n),其余操作都是 O(1)。push:入栈操作,需要将queue1中的 n 个元素出队,并入队 n+1个元素到 queue2,总计 2n+1 次操作。每次出队和入队操作的时间复杂度都是 O(1),因此入栈操作的时间复杂度是O(n)。pop:出栈操作,只是将queue1的队首元素出队,时间复杂度是O(1)。top:获得栈顶元素,对应获得queue1的队首元素,时间复杂度是O(1)。isEmpty:判断栈是否为空,只需要判断queue1是否为空,时间复杂度是 O(1)。
  • 空间复杂度:O(n),其中 n 是栈内的元素。需要使用两个队列存储栈内的元素。

方法二:一个队列实现

当一个新的元素x压栈时,其实我们可以不借助辅助队列,而是让它直接入队queue1,它会添加在队尾。然后接下来,只要将之前的所有数据依次出队再重新入队添加进queue1,就自然让x移动到队首。
代码演示如下:

// 用一个队列实现自定义栈
public class MyStack2 {
    // 定义一个队列
    Queue<Integer> queue;

    public MyStack2() {
        queue = new LinkedList<>();
    }

    public void push(int x){
        // 1. 首先记录当前队列长度
        int l = queue.size();

        // 2. 把x入队
        queue.offer(x);

        // 3. 把queue中原先的所有元素依次出队,然后再入队
        for (int i = 0; i < l; i++)
            queue.offer( queue.poll() );
    }

    public int pop(){
        return queue.poll();
    }

    public int top(){
        return queue.peek();
    }

    public boolean empty(){
        return queue.isEmpty();
    }
}

复杂度分析

  1. 时间复杂度:入栈操作 O(n),其余操作都是 O(1)。 push:入栈操作,需要将queue1中的 n 个元素出队,并入队 n+1个元素到 queue2,总计 2n+1 次操作。每次出队和入队操作的时间复杂度都是 O(1),因此入栈操作的时间复杂度是 O(n)。pop:出栈操作。只是将queue1的队首元素出队,时间复杂度是 O(1)。top:获得栈顶元素,对应获得queue1的队首元素,时间复杂度是 O(1)。isEmpty:判断栈是否为空,只需要判断queue1是否为空,时间复杂度是 O(1)。
  2. 空间复杂度:O(n),其中 n 是栈内的元素。需要使用两个队列存储栈内的元素。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值