数据结构与算法 - 双端队列

1. 概述

双端队列、队列、栈对比

定义特点
队列一端删除(头),另一端添加(尾)First In First Out
一端删除和添加(顶)Last In First Out
双端队列两端都可以删除、添加
优先级队列优先级高者先出队
延时队列根据延时时间确定优先级
并发非阻塞队列队列空或满时不阻塞
并发阻塞队列队列空时删除阻塞、队列满时添加阻塞

注1:Java中LinkedList即为典型双端队列实现,不过它同时实现了Queue接口,也提供了栈的push、pop等方法。

注2:不同语言,操作双端队列的方法命名有所不同,参见下表

操作JavaJavaScriptC++LeetCode 641
尾部插入offerLastpushpush_backinsertLast
头部插入offerFirstunshiftpush_frontinsertFront
尾部移除pollLastpoppop_backdeleteLast
头部移除pollFirstshiftpop_frontdeleteFront
尾部获取peekLastat(-1)backgetRear
头部获取peekFirstat(0)frontgetFront


接口定义

package com.itheima.datastructure.deque;

/**
 * 双端队列接口
 * @param <E> 队列中元素类型
 */
public interface Deque<E> {

    boolean offerFirst(E e);

    boolean offerLast(E e);

    E pollFirst();

    E pollLast();

    E peekFirst();

    E peekLast();

    boolean isEmpty();

    boolean isFull();
}

2. 基于双向环形链表实现

package com.itheima.datastructure.Deque;

import java.util.Iterator;

public class LinkedListDeque<E> implements Deque<E>, Iterable<E> {
    static class Node<E> {
        Node<E> prev;
        E value;
        Node<E> next;

        public Node(Node<E> prev, E value, Node<E> next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    int capacity;
    int size;
    Node<E> sentinel = new Node<>(null,null, null);

    public LinkedListDeque(int capacity) {
        this.capacity = capacity;
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }

    @Override
    public boolean offerFirst(E e) {
        if(isFull()) {
            return false;
        }
        // 处理4个指针
        Node<E> a = sentinel;
        Node<E> b = sentinel.next;
        Node<E> added = new Node<>(a, e, b);
        a.next = added;
        b.prev = added;
        size++;
        return true;
    }

    // a added b
    @Override
    public boolean offerLast(E e) {
        if(isFull()) {
            return false;
        }

        Node<E> b = sentinel;
        Node<E> a = sentinel.prev;
        Node<E> added = new Node<>(a, e, b);
        a.next = added;
        b.prev = added;
        size++;
        return true;
    }

    @Override
    public E pollFirst() {
        if(isEmpty()) {
            return null;
        }

        Node<E> a = sentinel;
        Node<E> removed = sentinel.next;
        Node<E> b = sentinel.next.next;
        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

    @Override
    public E pollLast() {
        if(isEmpty()) {
            return null;
        }

        Node<E> a = sentinel.prev.prev;
        Node<E> removed = sentinel.prev;
        Node<E> b = sentinel;
        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

    @Override
    public E peekFirst() {
        if(isEmpty()) {
            return null;
        }
        return sentinel.next.value;
    }

    @Override
    public E peekLast() {
        if(isEmpty()) {
            return null;
        }

        return sentinel.prev.value;
    }

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

    @Override
    public boolean isFull() {
        return size == capacity;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = sentinel.next;
            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }
}

3. 基于循环数组实现

package com.itheima.datastructure.Deque;

import java.util.Iterator;

public class ArrayDeque1<E> implements Deque<E>, Iterable<E>{
    E[] array;
    int head;
    int tail;

    public ArrayDeque1(int capacity) {
        array = (E[]) new Object[capacity + 1];  // 留一个空位置
    }
    
    static int inc(int i, int length) {
        if(i + 1 >= length) {
            return 0;
        }
        return i + 1;
    }
    
    static int dec(int i, int length) {
        if(i - 1 < 0) {
            return length - 1;
        }
        return i - 1;
    }

    @Override
    public boolean offerFirst(E e) {
        if(isFull()) {
            return false;
        }
        // head先往前一个位置,再存放元素
        head = dec(head, array.length);
        array[head] = e;
        return true;
    }

    @Override
    public boolean offerLast(E e) {
        if(isFull()) {
            return false;
        }
        // 先存放元素到tail所在位置,再将tail往后一个位置
        array[tail] = e;
        tail = inc(tail, array.length);
        return true;
    }

    @Override
    public E pollFirst() {
        if(isEmpty()) {
            return null;
        }
        // 先获取head位置的元素,然后将其置空,并将head往后移一个位置
        E e = array[head];
        array[head] = null;
        head = inc(head, array.length);
        return e;
    }

    @Override
    public E pollLast() {
        if(isEmpty()) {
            return null;
        }
        // 先将tail的位置往前移一位,获取tail位置的元素并将其置空
        tail = dec(tail, array.length);
        E e = array[tail];
        array[tail] = null;
        return e;
    }

    @Override
    public E peekFirst() {
        if(isEmpty()) {
            return null;
        }
        return array[head];
    }

    @Override
    public E peekLast() {
        if(isEmpty()) {
            return null;
        }
        return array[tail - 1];
    }

    @Override
    public boolean isEmpty() {
        return head == tail;
    }

    @Override
    public boolean isFull() {
        if(tail > head) {
            return tail - head == array.length - 1;
        } else if(tail < head) {
            return head - tail == 1;
        } else {
            return false;
        }
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int p = head;
            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public E next() {
                E e = array[p];
                p = inc(p, array.length);
                return e;
            }
        };
    }
}

4. 习题

4.1 二叉树Z字层序遍历

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]

示例 2:

输入:root = [1]
输出:[[1]]

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目在范围 [0, 2000] 内
  • -100 <= Node.val <= 100

解法一:双端队列 + 二叉树层序遍历

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root); // 根节点入队
        boolean odd = true;  // 判断是否是奇数行
        int c1 = 1;  // 当前层的结点数

        while (!queue.isEmpty()) {
            int c2 = 0;
            LinkedList<Integer> deque = new LinkedList<>();  // 存储当前层
            for (int i = 0; i < c1; i++) {
                TreeNode n = queue.poll();
                if (odd) {  // 奇数行,从左往右
                    deque.offerLast(n.val);  // 尾插
                } else {  // 偶数行,从右往左
                    deque.offerFirst(n.val);  // 头插
                }

                if (n.left != null) {
                    queue.offer(n.left);
                    c2++;
                }
                if (n.right != null) {
                    queue.offer(n.right);
                    c2++;
                }
            }
            c1 = c2;
            odd = !odd;
            result.add(deque);
        }
        return result;
    }
}

4.2 设计双端循环队列

设计实现双端队列。

实现 MyCircularDeque 类:

  • MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
  • boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
  • boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
  • boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
  • boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
  • int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
  • int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。
  • boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false  。
  • boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。

示例 1:

输入
["MyCircularDeque", "insertLast", "insertLast", "insertFront", "insertFront", "getRear", "isFull", "deleteLast", "insertFront", "getFront"]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
输出
[null, true, true, true, false, 2, true, true, true, 4]

解释
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1);			        // 返回 true
circularDeque.insertLast(2);			        // 返回 true
circularDeque.insertFront(3);			        // 返回 true
circularDeque.insertFront(4);			        // 已经满了,返回 false
circularDeque.getRear();  				// 返回 2
circularDeque.isFull();				        // 返回 true
circularDeque.deleteLast();			        // 返回 true
circularDeque.insertFront(4);			        // 返回 true
circularDeque.getFront();				// 返回 4

提示:

  • 1 <= k <= 1000
  • 0 <= value <= 1000
  • insertFrontinsertLastdeleteFrontdeleteLastgetFrontgetRearisEmptyisFull  调用次数不大于 2000 次
class MyCircularDeque {  
    static class Node {  
        Node prev;  
        int value;  
        Node next;  

        public Node(Node prev, int value, Node next) {  
            this.prev = prev;  
            this.value = value;  
            this.next = next;  
        }  
    }  

    int capacity;  
    int size = 0;  
    Node sentinel;  

    public MyCircularDeque(int k) {  
        this.capacity = k;  
        sentinel = new Node(null, 0, null); // Node中应该有一个默认值,这里使用0  
        sentinel.next = sentinel;  
        sentinel.prev = sentinel;  
    }  
    
    public boolean insertFront(int value) {  
        if (isFull()) {  
            return false;  
        }  
        Node added = new Node(sentinel, value, sentinel.next);  
        sentinel.next.prev = added;  
        sentinel.next = added;  
        size++;  
        return true;  
    }  
    
    public boolean insertLast(int value) {  
        if (isFull()) {  
            return false;  
        }  
        Node added = new Node(sentinel.prev, value, sentinel);  
        sentinel.prev.next = added;  
        sentinel.prev = added;  
        size++;  
        return true;  
    }  
    
    public boolean deleteFront() {  
        if (isEmpty()) {  
            return false; // 应该返回 false,而不是 null  
        }  
        Node front = sentinel.next;  
        sentinel.next = front.next;  
        front.next.prev = sentinel;  
        size--;  
        return true;  
    }  
    
    public boolean deleteLast() {  
        if (isEmpty()) {  
            return false; // 应该返回 false,而不是 null  
        }  
        Node rear = sentinel.prev;  
        sentinel.prev = rear.prev;  
        rear.prev.next = sentinel;  
        size--;  
        return true;  
    }  
    
    public int getFront() {  
        if (isEmpty()) {  
            return -1; // 返回-1表示队列为空  
        }  
        return sentinel.next.value;  
    }  
    
    public int getRear() {  
        if (isEmpty()) {  
            return -1; // 返回-1表示队列为空  
        }  
        return sentinel.prev.value;  
    }  
    
    public boolean isEmpty() {  
        return size == 0;  
    }  
    
    public boolean isFull() {  
        return size == capacity;  
    }  
}  

/**  
 * Your MyCircularDeque object will be instantiated and called as such:  
 * MyCircularDeque obj = new MyCircularDeque(k);  
 * boolean param_1 = obj.insertFront(value);  
 * boolean param_2 = obj.insertLast(value);  
 * boolean param_3 = obj.deleteFront();  
 * boolean param_4 = obj.deleteLast();  
 * int param_5 = obj.getFront();  
 * int param_6 = obj.getRear();  
 * boolean param_7 = obj.isEmpty();  
 * boolean param_8 = obj.isFull();  
 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值