Skip to content

225.用队列实现栈

难度:容易

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

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

注意:

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

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:

  • 1 <= x <= 9
  • 最多调用100pushpoptopempty
  • 每次调用 poptop 都保证栈不为空

**进阶:**你能否仅用一个队列来实现栈。

解题思路

这是一道模拟题,不涉及到具体算法,考察的就是对栈和队列的掌握程度。

队列的特性是 FIFO(先入先出),而栈的特性是 FILO(先入后出)。

知道两者特性之后,我们只需要用 一个队列 来模拟栈的特性就可以了。

  1. push 数据的时候,将数据添加到队列尾部

  2. pop 数据的时候,将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,再从队列头部弹出原来在队列尾部的元素

  3. top 和 pop 函数功能类似,区别在于弹出原来在队列尾部的元素之后,还要将该元素重新添加到队列尾部

  4. 判断栈是否为空,只需要判断队列是否为空即可

注意:我们虽然使用双端队列 Deque,但这里队列只能使用单向队列的特性。

我的代码

java
class MyStack {
    Deque<Integer> deque;

    public MyStack() {
        deque = new LinkedList<Integer>();
    }

    public void push(int x) {
        deque.push(x);
    }

    public int pop() {
        for (int i = 0; i < deque.size() - 1; i++) {
            Integer x = deque.pollFirst();
            deque.push(x);
        }
        return deque.pollFirst();
    }

    public int top() {
        Integer x = null;
        for (int i = 0; i < deque.size(); i++) {
            x = deque.pollFirst();
            deque.push(x);
        }
        return x;
    }

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

时间复杂度:push() 操作为 O(1),pop()peek() 操作都是 O(n)

空间复杂度:O(n)

另一种做法

上述做法中 poptop 方法中的遍历操作导致了较高的时间复杂度。

这是因为每次执行这些操作时,都需要遍历整个队列以达到栈顶元素。

如何使 poptop 操作更加高效呢?我们可以从 push 操作入手,保证队列前端的元素是最后入栈的元素即可

push 操作时,首先获得 push 前队列内的元素个数 n,然后将元素添加到队尾,再将队列中的前 n 个元素(即除了新入栈的元素之外的全部元素)依次从队列头部弹出并添加到队尾,此时队列的前端的元素即为新入栈的元素,且 队列的前端和后端分别对应栈顶和栈底

由于每次 push 操作都确保了队列的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除队列的前端元素并返回即可,获得栈顶元素操作只需要获得队列的前端元素并返回即可(不用移除元素)。

代码展示

java
class MyStack {
    Deque<Integer> deque;

    public MyStack() {
        deque = new LinkedList<Integer>();
    }

    public void push(int x) {
        deque.push(x);
        for (int i = 0; i < deque.size() - 1; i++) {
            x = deque.pollFirst();
            deque.push(x);
        }
    }

    public int pop() {
        return deque.pollFirst();
    }

    public int top() {
        return deque.peekFirst();
    }

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

时间复杂度:push() 操作为 O(n),pop()peek() 操作都是 O(1)

空间复杂度:O(n)

总结

在工业级别代码开发中,最忌讳的就是 实现一个类似的函数,直接把代码粘过来改一改就完事了。

这样的项目代码会越来越乱,一定要懂得复用,功能相近的函数要抽象出来,不要大量的复制粘贴,很容易出问题!(踩过坑的人自然懂)

工作中如果发现某一个功能自己要经常用,同事们可能也会用到,自己就花点时间把这个功能抽象成一个好用的函数或者工具类,不仅自己方便,也方便了同事们。

同事们就会逐渐认可你的工作态度和工作能力,自己的口碑都是这么一点一点积累起来的!在同事圈里口碑起来了之后,你就发现自己走上了一个正循环,以后的升职加薪才少不了你!