Loading

栈和队列相关的一些问题

栈和队列相关的一些问题

作者:Grey

原文地址:

博客园:栈和队列相关的一些问题

CSDN:栈和队列相关的一些问题

最小栈

题目链接见:LeetCode 155. Min Stack

主要思路

准备两个栈,一个栈叫 stack, 用于记录原始数据信息; 一个栈叫 min,用来存此时原始栈中的最小值,和 stack 同步增长,只不过 min 栈在每次 push 的时候,要用当前值和 min 栈顶部元素比较一下,如果 min 顶部元素较小,则 min 栈不 push 原始数,而是再次 push 一次 min 顶部的元素,这样每次 min 顶部的元素,就是原始栈中最小的那个值;

此外,弹出的时候,min 栈跟随 stack 栈同步弹出元素即可。

完整代码如下

class MinStack {
    Stack<Integer> stack;
    Stack<Integer> minStack;
    public MinStack() { 
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || minStack.peek() > val) {
            minStack.push(val);
        } else {
            minStack.push(minStack.peek());
        }
    }
    
    public void pop() {
        minStack.pop();
        stack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

用双链表实现栈和队列

主要思路:使用双向链表实现一个双端队列,然后用双端队列去实现栈和队列。

双向链表的数据结构定义为

private final static class Node<T> {
    public T data;
    // 下一个节点
    public Node<T> next;
    // 上一个节点
    public Node<T> last;

    public Node(T data) {
        this.data = data;
    }
}

双端队列(可以从头部进,头部出,也可以从尾部进,尾部出)的数据结构定义如下:

public final static class DoubleEndsQueue<T> {
    public Node<T> head;
    public Node<T> tail;
    
    public void addFromHead(T value) {
        // TODO
        // 从头部进入
    }

    public void addFromBottom(T value) {
        // TODO
        // 从尾部进入
    }

    public T popFromHead() {
        // TODO
        // 从头部弹出
    }

    public T popFromBottom() {
        // TODO
        // 从尾部弹出
    }

    public boolean isEmpty() {
        return head == null || tail == null;
    }

}

如果我们可以实现上述双端队列,那么实现栈和队列就很简单了,不赘述,直接上代码。

使用自定义双端队列实现栈结构代码如下:

public final static class MyStack<T> {
    private DoubleEndsQueue<T> queue;

    public MyStack() {
        queue = new DoubleEndsQueue<T>();
    }

    public void push(T value) {
        queue.addFromHead(value);
    }

    public T pop() {
        if (null == queue || isEmpty()) {
            return null;
        }
        return queue.popFromHead();
    }

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

}

使用自定义双端队列实现队列结构代码如下:

public final static class MyQueue<T> {
    private DoubleEndsQueue<T> queue;

    public MyQueue() {
        queue = new DoubleEndsQueue<>();
    }

    public void push(T value) {
        if (null == queue) {
            return;
        }
        queue.addFromHead(value);
    }

    public T poll() {
        if (isEmpty()) {
            return null;
        }
        return queue.popFromBottom();
    }

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

双端队列的几个主要方法

void addFromHead(T value): 从头部进入

主要思路

  1. 如果链表没初始化,则新建头节点和尾部节点。

  2. 如果链表已经有元素了,则新建一个元素,并连上头节点,然后把头节点上一个节点设置为新建节点。

  3. 头节点指向新建节点。

代码如下

public void addFromHead(T value) {
    Node<T> node = new Node<>(value);
    if (head == null) {
        tail = node;
    } else {
        node.next = head;
        head.last = node;
    }
    head = node;
}

void addFromBottom(T value): 从尾部进入

主要思路

  1. 如果链表没初始化,则新建头节点和尾部节点。

  2. 如果链表已经有元素了,则新建一个元素,尾部节点的下一个节点设置为这个新建节点,这个新建节点的上一个节点指向尾节点。

  3. 尾节点指向新建节点。

代码如下

public void addFromBottom(T value) {
    Node<T> node = new Node<>(value);
    if (tail == null) {
        head = node;
    } else {
        tail.next = node;
        node.last = tail;
    }
    tail = node;
}

T popFromHead(): 从头部弹出

主要思路:

  1. 如果是空链表,直接返回 null。

  2. 如果非空,但是只有一个节点,则把头部和尾部节点都置为空即可。

  3. 如果不止一个节点,则先找到头节点的下一个节点,然后把这个节点的上一个节点置为空,然后把头节点指向其下一个节点。

代码如下

public T popFromHead() {
    if (null == head || tail == null) {
        return null;
    }
    T data = head.data;
    if (head == tail) {
        head = null;
        tail = null;
        return data;
    }
    head = head.next;
    head.last = null;

    return data;

}

T popFromBottom(): 从尾部弹出

主要思路:

  1. 如果是空链表,直接返回 null。

  2. 如果非空,但是只有一个节点,则把头部和尾部节点都置为空即可。

  3. 如果不止一个节点,则先找到尾节点的上一个节点,然后把这个节点的下一个节点置为空,然后把尾节点指向其上一个节点。

主要代码

public T popFromBottom() {
    if (tail == null || head == null) {
        return null;
    }
    T data = tail.data;
    if (tail == head) {
        tail = null;
        head = null;
        return data;
    }
    tail = tail.last;
    tail.next = null;

    return data;
}

LeetCode 有同样的题目,链接见:LeetCode 641. Design Circular Deque

注:LeetCode 这题需要多引入两个变量,一个是 size ,用于标识当前链表长度,一个是 capacity, 标识当前队列可以扩展的最大长度。其余方法和上述例子一样处理。

LeetCode 641. Design Circular Deque 这题完整代码如下


public class LeetCode_0641_DesignCircularDeque {

    class MyCircularDeque {
        // 双向链表
        static class DoubleNode {
            public int val;
            public DoubleNode next;
            public DoubleNode last;

            public DoubleNode(int v) {
                val = v;
            }
        }

        // 头节点
        DoubleNode head;
        // 尾节点
        DoubleNode tail;
        // 当前元素数量
        int size;
        // 容量
        int capacity;

        public MyCircularDeque(int k) {
            capacity = k;
        }

        // 头部进
        public boolean insertFront(int value) {
            if (isFull()) {
                return false;
            }
            if (isEmpty()) {
                // 链表是空的,先初始化
                head = tail = new DoubleNode(value);
            } else {
                // 新建节点
                DoubleNode newHead = new DoubleNode(value);
                newHead.next = head;
                head.last = newHead;
                head = newHead;
            }
            size++;
            return true;
        }

        // 尾部进
        public boolean insertLast(int value) {
            if (isFull()) {
                return false;
            }
            if (isEmpty()) {
                head = tail = new DoubleNode(value);
            } else {
                DoubleNode rearNode = new DoubleNode(value);
                tail.next = rearNode;
                rearNode.last = tail;
                tail = rearNode;
            }
            size++;
            return true;
        }

        // 头部出
        public boolean deleteFront() {
            if (isEmpty()) {
                return false;
            }
            if (size == 1) {
                head = tail = null;
            } else {
                // 不止一个元素
                DoubleNode newHead = head.next;
                newHead.last = null;
                head = newHead;
            }
            size--;
            return true;
        }

        // 尾部出
        public boolean deleteLast() {
            if (isEmpty()) {
                return false;
            }
            if (size == 1) {
                head = tail = null;
            } else {
                DoubleNode newRear = tail.last;
                newRear.next = null;
                tail = newRear;
            }
            size--;
            return true;
        }

        // 获取头部元素
        public int getFront() {
            if (isEmpty()) {
                return -1;
            } else {
                return head.val;
            }
        }

        // 获取尾部元素
        public int getRear() {
            if (isEmpty()) {
                return -1;
            } else {
                return tail.val;
            }
        }

        // 判断链表是否为空
        public boolean isEmpty() {
            return size == 0;
        }

        // 判断链表是否满
        public boolean isFull() {
            return size == capacity;
        }
    }
}

使用栈来实现队列

题目描述见:LeetCode 232. Implement Queue using Stacks

主要思路

使用两个栈,一个是 push 栈,一个是 pop 栈,通过这两个栈互相导入的方式实现队列操作,每次入队列的元素,会直接存到 push 栈中,在队列弹出数据的时候,从 pop 栈中弹出,不过弹出需要按如下规则来处理

  1. 先将 push 栈中的内容一次性导入到 pop 栈中,然后 pop 栈弹出的元素,即为队列要弹出的元素。

  2. 只有 pop 栈空了才能导数据。

  3. pop 栈不为空不用导数据。

完整代码见

class MyQueue {
    private final Stack<Integer> push;
    private final Stack<Integer> pop;

    public MyQueue() {
        push = new Stack<>();
        pop = new Stack<>();
    }

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

    public int pop() {
        while (!push.isEmpty()) {
            pop.push(push.pop());
        }
        int result = pop.pop();
        while (!pop.isEmpty()) {
            push.push(pop.pop());
        }
        return result;
    }

    public int peek() {
        while (!push.isEmpty()) {
            pop.push(push.pop());
        }
        int result = pop.peek();
        while (!pop.isEmpty()) {
            push.push(pop.pop());
        }
        return result;
    }

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

使用队列来实现栈

题目描述见:LeetCode 225. Implement Stack using Queues

主要思路(使用两个队列)

使用两个队列,一个队列是 data 队列,一个队列是 help 队列,每次栈要存入数据的时候,其实是存在 data 队列中,但是栈在弹出数据的时候,要先把 data 队列里面的数据先一一存入 help 队列,然后把最后一个要存入的数据弹出,然后把 data 和 help 两个队列互换,在下一轮操作中,继续以上流程操作即可。

完整代码见

class MyStack {
    private Queue<Integer> data;
    private Queue<Integer> help;

    public MyStack() {
        data = new LinkedList<>();
        help = new LinkedList<>();
    }

    // 从尾部进
    public void push(int x) {
        data.offer(x);
    }

    public int pop() {
        int result = 0;
        while (!data.isEmpty()) {
            int x = data.poll();
            if (data.isEmpty()) {
                result = x;
            } else {
                help.offer(x);
            }
        }
        Queue<Integer> t = data;
        data = help;
        help = t;
        return result;
    }

    public int top() {
        int result = 0;
        while (!data.isEmpty()) {
            int x = data.poll();
            help.offer(x);
            if (data.isEmpty()) {
                result = x;
            }
        }
        Queue<Integer> t = data;
        data = help;
        help = t;
        return result;
    }

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

主要思路(使用一个队列)

本题的 follow-up 要求是:如何使用一个队列来实现栈的功能,由于没有辅助的队列,所以需要提前记录队列中元素的数量,用这个变量来控制入队列的时机,完整代码如下:

class MyStack {
    // only use poll() and offer()
    private final Queue<Integer> data;

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

    // O(n)
    public void push(int x) {
        int size = data.size();
        data.offer(x);
        // 其他元素都移动到 x 后面
        for (int i = 0; i < size; i++) {
            data.offer(data.poll());
        }
    }

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

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

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

数组实现不超过固定大小的循环队列

题目链接见:LeetCode 622. Design Circular Queue

使用数组可以实现,但是需要一些辅助变量,首先需要这两个变量来判断队列是否为空或者已经满了

// 当前队列的元素有多少了
private int size;
// 当前队列可以支持的最大元素个数是多少
// 这个变量可以用数组长度来替代
private final int limit;

其次,需要定义两个指针,用于标识当前的出队列位置和当前的入队列位置。

// 当前的出队列位置
private int popIndex;
// 当前的入队列位置
private int pushIndex;

核心方法是如下两个

// 入队列
public boolean enQueue(int value) {
    if (isFull()) {
        // 满了无法入队列
        return false;
    }
    // 元素增加1
    size++;
    // 入队列的位置把元素填上
    arr[pushIndex] = value;
    // 尾指针当前应该是入队列的位置
    rear = pushIndex;
    // 下一个入队列的位置
    pushIndex = next(pushIndex);
    return true;
}

// 出队列
public boolean deQueue() {
    if (isEmpty()) {
        // 空了怎么入队 
        return false;
    }
    // 元素减少一个
    size--;
    // 到下一个出队列的位置即可
    // 不需要处理当前出队列的位置,因为后续这个位置会被新的元素覆盖
    popIndex = next(popIndex);
    return true;
}

其中的 next方法就是循环下标的意思

// 循环下标
// 0,1,2,3...N, 0, 1, 2....N, 0, 1,2...
private int next(int pre) {
    return pre < limit - 1 ? pre + 1 : 0;
}

完整代码见

class MyCircularQueue {
    private final int[] arr;
    // private final int limit; // 容量,和arr大小保持一致,可以省略
    private int popIndex; // 队列头部指针(指向队列第一个有效元素)
    private int pushIndex; // 队列尾部的下一个位置(就是待插入元素的位置)
    private int rear; // 队列尾部指针
    private int size; // 非常重要,标识现在的有效元素有几个,用于判断队列是否满/空

    public MyCircularQueue(int k) {
        arr = new int[k];
    }

    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        size++;
        arr[pushIndex] = value;
        rear = pushIndex;
        pushIndex = next(pushIndex);
        return true;
    }

    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        size--;
        popIndex = next(popIndex);
        return true;
    }

    public int Front() {
        return isEmpty() ? -1 : arr[popIndex];
    }

    public int Rear() {
        return isEmpty() ? -1 : arr[rear];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == arr.length;
    }

    // 最重要的方法:判断下一个位置,模拟环行为
    private int next(int pre) {
        return pre + 1 < arr.length ? (pre + 1) : 0;
    }
}

更多

算法和数据结构学习笔记

算法和数据结构学习代码

posted @ 2022-08-27 22:16  Grey Zeng  阅读(360)  评论(0编辑  收藏  举报