Day10~232.用栈实现队列、225.用队列实现栈

119 阅读3分钟

摘要

本文主要探讨了栈和队列的理论基础,并提供了LeetCode上的一道经典问题:225.用队列实现栈的解题思路和代码。

1、栈和队列的理论基础

1.1 Java中的栈LinkedList

在Java中,你可以使用LinkedList来实现栈数据结构。LinkedList是Java集合框架中的一个类,它实现了List接口,但也可以用作栈,因为它支持在列表的开头和末尾进行高效插入和删除操作,非常适合栈的操作。

下面是如何使用LinkedList来实现栈的基本操作:

  1. 创建一个LinkedList对象:

    LinkedList<Integer> stack = new LinkedList<>();
    
  2. 入栈操作: 使用push方法将元素推入栈顶。

    stack.push(1);
    stack.push(2);
    stack.push(3);
    
  3. 出栈操作: 使用pop方法从栈顶弹出并返回元素。

    int topElement = stack.pop(); // 弹出并返回栈顶元素
    
  4. 查看栈顶元素: 使用peek方法查看但不移除栈顶元素。

    int topElement = stack.peek(); // 查看栈顶元素
    
  5. 检查栈是否为空: 使用isEmpty方法检查栈是否为空。

    boolean isEmpty = stack.isEmpty(); // 检查栈是否为空
    
  6. 获取栈的大小: 使用size方法获取栈中元素的数量。

    int size = stack.size(); // 获取栈的大小
    
  7. 遍历栈元素: 通常使用增强的for循环来遍历栈元素。

    for (Integer element : stack) {
        // 处理每个元素
    }
    
  8. 注意事项:

    • LinkedList可以用作栈,但也可以用作队列或双端队列,因此要确保只使用栈的操作。
    • 在出栈前检查栈是否为空,以避免出现栈空异常。
    • 使用LinkedList实现的栈是线程安全的,适用于单线程环境。如果在多线程环境中使用栈,建议使用java.util.concurrent包中的ConcurrentLinkedDeque,它是线程安全的。

这些是使用LinkedList实现栈的基本操作和注意事项。LinkedList通常用于栈和队列等数据结构的实现,因为它提供了高效的插入和删除操作。

1.2 Java中的队列LinkedList

在Java中,你可以使用LinkedList来实现队列数据结构。LinkedList是Java集合框架中的一个类,它实现了List接口,但也可以用作队列,因为它支持在列表的开头和末尾进行高效插入和删除操作,非常适合队列的操作。

下面是如何使用LinkedList来实现队列的基本操作:

  1. 创建一个LinkedList对象:

    LinkedList<Integer> queue = new LinkedList<>();
    
  2. 入队操作: 使用offer方法将元素添加到队列的末尾。

    queue.offer(1);
    queue.offer(2);
    queue.offer(3);
    
  3. 出队操作: 使用poll方法从队列的开头删除并返回元素。

    int frontElement = queue.poll(); // 从队列开头删除并返回元素
    
  4. 查看队列的头元素: 使用peek方法查看但不移除队列的头元素。

    int frontElement = queue.peek(); // 查看队列的头元素
    
  5. 检查队列是否为空: 使用isEmpty方法检查队列是否为空。

    boolean isEmpty = queue.isEmpty(); // 检查队列是否为空
    
  6. 获取队列的大小: 使用size方法获取队列中元素的数量。

    int size = queue.size(); // 获取队列的大小
    
  7. 遍历队列元素: 通常使用增强的for循环来遍历队列元素。

    for (Integer element : queue) {
        // 处理每个元素
    }
    
  8. 注意事项:

    • LinkedList可以用作队列,但也可以用作栈、双端队列或普通列表,因此要确保只使用队列的操作。
    • 在出队前检查队列是否为空,以避免出现队列空异常。
    • 使用LinkedList实现的队列是线程安全的,适用于单线程环境。如果在多线程环境中使用队列,建议使用java.util.concurrent包中的ConcurrentLinkedQueue,它是线程安全的。

这些是使用LinkedList实现队列的基本操作和注意事项。LinkedList通常用于队列、栈、双端队列等数据结构的实现,因为它提供了高效的插入和删除操作。

1.3 Java中的队列ArrayDeque

ArrayDeque是Java中的一个双端队列(deque)实现,它可以用来实现队列的操作,但不包括栈的操作。下面是如何在Java中使用ArrayDeque来实现队列的基本操作:

1. 创建一个ArrayDeque对象:

ArrayDeque<Integer> queue = new ArrayDeque<>();

2. 入队操作: 使用offer方法将元素添加到队列的末尾。

queue.offer(1);
queue.offer(2);
queue.offer(3);

3. 出队操作: 使用poll方法从队列的开头删除并返回元素。

int frontElement = queue.poll(); // 从队列开头删除并返回元素

4. 查看队列的头元素: 使用peek方法查看但不移除队列的头元素。

int frontElement = queue.peek(); // 查看队列的头元素

5. 检查队列是否为空: 使用isEmpty方法检查队列是否为空。

boolean isEmpty = queue.isEmpty(); // 检查队列是否为空

6. 获取队列的大小: 使用size方法获取队列中元素的数量。

int size = queue.size(); // 获取队列的大小

7. 遍历队列元素: 可以使用增强的for循环来遍历队列元素。

for (Integer element : queue) {
    // 处理每个元素
}

8. 注意事项:

  • ArrayDeque是非线程安全的,适用于单线程环境。
  • 在出队前检查队列是否为空,以避免出现队列空异常。

这些是使用ArrayDeque实现队列的基本操作和注意事项。ArrayDeque通常用于需要高效的插入和删除操作的队列场景。如果要实现栈的操作,可以使用ArrayDequepushpop方法。

1.4 LinkedList vs. ArrayDeque

在Java中,LinkedListArrayDeque都可以用来实现队列,但它们有一些区别:

  1. 底层数据结构:

    • LinkedList是一个双向链表,每个元素都包含了指向前一个元素和后一个元素的引用。
    • ArrayDeque是一个基于动态数组的双端队列,底层使用数组实现。
  2. 性能特点:

    • LinkedList在插入和删除操作(包括队列的入队和出队)上通常比较慢,因为它需要调整链表中的引用。
    • ArrayDeque在插入和删除操作上通常更快,因为它使用数组,可以在常数时间内执行这些操作。
  3. 空间复杂度:

    • LinkedList通常需要更多的内存空间来存储链表节点对象和相关引用。
    • ArrayDeque通常需要较少的内存,因为它只需要存储数组元素和一些额外的控制信息。
  4. 随机访问:

    • LinkedList不支持高效的随机访问,因为要遍历链表以查找特定位置的元素。
    • ArrayDeque支持高效的随机访问,因为它基于数组,可以通过索引直接访问元素。
  5. 线程安全性:

    • 无论是LinkedList还是ArrayDeque,它们都不是线程安全的,如果在多线程环境中使用,需要自行考虑同步或使用线程安全的队列实现。
  6. 功能特性:

    • LinkedList可以用于双向队列的操作,因为它支持在队列头部和尾部进行高效的插入和删除。
    • ArrayDeque是一个专门设计用于双端队列的数据结构,因此通常在队列操作上更为高效。

综上所述,ArrayDeque通常更适合用作队列的实现,因为它在插入和删除操作上通常更快,而LinkedList适合那些需要双向链表特性的场景。选择哪个取决于你的具体需求和性能要求。如果你只需要一个普通队列,ArrayDeque可能是更好的选择。

2、232.用栈实现队列

2.1 思路

可以类比成一堆书,首先将所有的书按照一定的顺序依次放入位置A,接着再将这些从位置A取出的书依次放入位置B。此时,第一次放入A位置的书一定会被第一个从位置B取出,这符合了队列的先进先出原则。

  • 栈:后进先出;队列:先进先出
  • stack1 负责出队列,stack2 负责进队列
  • 比如1->2->3 依次加入到stack2 中[1, 2, 3]
  • 当需要出站时,stack2 的所有元素加入到 stack1 [3, 2, 1]
  • stack1 出站依次为1->2->3

2.2 代码

class MyQueue {
​
    private LinkedList<Integer> stack1;
    private LinkedList<Integer> stack2;
​
    public MyQueue() {
        stack1 = new LinkedList<>();
        stack2 = new LinkedList<>();
    }
    
    public void push(int x) {
        stack2.push(x);
    }
    
    public int pop() {
        transform();
​
        return stack1.pop();
    }
    
    public int peek() {
        transform();
​
        return stack1.peek();
    }
    
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
    
    private void transform() {
        if(!stack1.isEmpty()) {
            return;
        }
​
        while(!stack2.isEmpty()) {
            stack1.push(stack2.pop());
        }
    }
​
}

3、225.用队列实现栈

3.1 使用两个队列

思路

可以将其类比为排队的场景。当有新人要加入队列时,首先将队列A中的人加入到队列B,然后将新人加入队列A中,接着再将队列B中的人加入队列A。这样,新加入的人会第一个出队列,依此类推,符合了栈的后进先出原则。

  • 栈:后进先出;队列:先进先出

  • queue1 作为和栈中保持一样元素的队列,queue2 作为辅助队列

  • 比如1->2->3 依次加入到queue1 时,使用辅助队列 queue2,使得queue1 的顺序为 [3, 2, 1]

  • 具体操作步骤如下:

    • 先将queue1 的元素加入到queue2
    • 接着加入该元素
    • 最后将queue2 的元素加入queue1

代码

class MyStack {
​
    private LinkedList<Integer> queue1;
    private LinkedList<Integer> queue2;
​
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public void push(int x) {
        // 先将queue1 的元素加入到queue2
        while(!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
​
        // 接着加入该元素
        queue1.offer(x);
​
        // 最后将queue2 的元素加入queue1
        while(!queue2.isEmpty()) {
            queue1.offer(queue2.poll());
        }
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}

3.2 使用一个队列

思路

这情景可以类比排队,当有新人要加入队列时,直接将新人加入队列,并将之前在队列的人再次加入队列。不管何时加入,新加入的人总是会第一个出队,遵循栈的后进先出原则。

  • 使用一个队列,当加入新元素时,先加入到队列,然后队列前面的元素(size-1)重新加入

代码

class MyStack {
​
    private LinkedList<Integer> queue1;
    
    public MyStack() {
        queue1 = new LinkedList<>();
    }
    
    public void push(int x) {
        queue1.offer(x);
​
        int size = queue1.size();
        while(--size > 0) {
            queue1.offer(queue1.poll());
        }
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}

参考资料

代码随想录-用栈实现队列

代码随想录-用队列实现栈