授课语音

实现栈的算法设计

栈(Stack)是一种常用的数据结构,它遵循“后进先出”(LIFO, Last In, First Out)的原则。也就是说,最后插入的元素最先被取出。在实现栈时,我们通常希望栈的操作,如 pushpop,能够在常数时间内完成。本文将介绍如何设计一个支持常数时间 pushpop 操作的栈。

1. 栈的基本操作

栈通常支持以下基本操作:

  • push(e):将元素 e 压入栈顶。
  • pop():将栈顶元素弹出,并返回该元素。
  • top():返回栈顶元素,但不弹出。
  • isEmpty():检查栈是否为空。
  • size():返回栈中元素的个数。

我们的目标是实现 pushpop 操作的时间复杂度为 O(1),即常数时间。


2. 设计思路

为了实现常数时间的 pushpop 操作,我们可以使用两个栈来解决。具体设计思路如下:

  1. 主栈(stack1):用于存储栈的元素。所有的 push 操作都发生在主栈中。
  2. 辅助栈(stack2):用于辅助 pop 操作。当我们从主栈中弹出元素时,如果辅助栈为空,则将主栈中的元素全部倒入辅助栈中。这样,辅助栈的顶部就是最先进入主栈的元素,实现了栈的后进先出特性。

关键点

  • push 操作:直接将元素压入主栈 stack1
  • pop 操作:如果辅助栈为空,先将主栈的所有元素倒入辅助栈。然后从辅助栈中弹出元素。

通过这种设计,我们可以保证 push 操作始终是常数时间 O(1),而 pop 操作平均也是常数时间 O(1),尽管在某些情况下 pop 操作需要将主栈的元素转移到辅助栈中,但这可以平均分摊到多个操作中。


3. 代码实现

import java.util.Stack;

public class MyStack {
    // 主栈,用于存储元素
    private Stack<Integer> stack1;
    // 辅助栈,用于帮助pop操作
    private Stack<Integer> stack2;

    // 构造函数,初始化栈
    public MyStack() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    // push操作:将元素压入栈顶
    public void push(int x) {
        stack1.push(x);  // 直接压入主栈
    }

    // pop操作:从栈顶弹出元素
    public int pop() {
        // 如果辅助栈为空,将主栈的元素倒入辅助栈
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());  // 主栈元素倒入辅助栈
            }
        }
        // 弹出辅助栈的元素
        return stack2.pop();
    }

    // top操作:获取栈顶元素,但不弹出
    public int top() {
        // 如果辅助栈为空,将主栈的元素倒入辅助栈
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());  // 主栈元素倒入辅助栈
            }
        }
        // 返回辅助栈的栈顶元素
        return stack2.peek();
    }

    // isEmpty操作:检查栈是否为空
    public boolean isEmpty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }

    // size操作:返回栈中元素的个数
    public int size() {
        return stack1.size() + stack2.size();
    }
}

4. 详细中文注释

  • 构造函数public MyStack()
    初始化主栈 stack1 和辅助栈 stack2

  • push(int x)
    将元素 x 压入主栈 stack1

  • pop()
    如果辅助栈为空,则将主栈中的所有元素倒入辅助栈,这样辅助栈的栈顶就是主栈最早压入的元素。然后从辅助栈弹出栈顶元素。

  • top()
    pop() 操作类似,先将主栈元素转移到辅助栈,然后返回辅助栈的栈顶元素。

  • isEmpty()
    检查主栈和辅助栈是否都为空,如果都为空,则栈为空。

  • size()
    返回主栈和辅助栈中元素的总数。


5. 复杂度分析

  • 时间复杂度

    • push(x):每次操作都在主栈中进行,时间复杂度是 O(1)
    • pop():如果辅助栈为空,则将主栈的元素倒入辅助栈,这个操作的时间复杂度是 O(n)(假设主栈中有 n 个元素),但是每个元素只会被移动一次,所以平均时间复杂度是 O(1)
    • top():与 pop() 操作类似,平均时间复杂度是 O(1)
  • 空间复杂度O(n),其中 n 是栈中元素的个数,因为栈中所有元素都需要存储在主栈和辅助栈中。


6. 总结

通过使用两个栈,我们设计了一种能够在常数时间内执行 push 操作,并且在平均常数时间内执行 poptop 操作的栈。这个设计方案在实际使用中能够高效地处理栈的基本操作,适合需要频繁进行栈操作的场景。

去1:1私密咨询

系列课程: