题目描述

牛客原题: ✅ 补充题 172. 用单链表实现队列

用单链表实现整数队列,提供以下操作:

  • offer:从尾部入队。
  • poll:从头部出队并返回该元素。
  • peek:读取队首元素,不删除。
  • isEmpty:判断队列是否为空。

各操作最坏时间复杂度为 O(1)。空队列查询或出队时,Java 返回 null,Go 返回 (value,false)。

示例 1:

输入: 操作 = [offer(3), offer(5), poll(), peek(), poll(), isEmpty()]
输出: 查询结果 = [3,5,5,true]
解释: offer 不返回查询值。首次出队得到 3,peek 读取 5 且不删除,第二次出队后队列为空。

提示:

  • 牛客通过 push、pop、front 操作驱动队列,对应本文的 offer、poll、peek;打印练习结果时将空队列的失败状态输出为 error。

  • 队列存储整数,先进先出,各操作最坏 O(1)。
  • 空队列读取或出队时,Java 返回 null,Go 返回失败标志。

题意分析

先进先出要求从链头删除、从链尾追加。若只保存头指针,入队寻找尾节点需要线性时间,因此同时保存 head、tail,使每次操作只改固定数量的指针。

解法:头尾指针维护链式队列

核心思路

[!blue]

非空时 head 指向最早入队节点,tail 指向最后入队节点且 tail.next 为空;空队列则两个指针同时为空。入队创建新节点,非空时接到尾部,空时同时建立头节点,最后更新 tail。

出队先保存头值,再令 head = head.next。若新头为空,说明最后一个节点已被删除,必须同步清空 tail,否则下一次入队会接到已脱离队列的旧节点上。

peek 只读头值,isEmpty 检查头指针;空队列用 null 或独立失败标志表示,不用某个整数值冒充空状态。所有入队、出队和查询都无需遍历。

解题步骤

  1. 初始化 head、tail 为空。
  2. 入队时把新节点接到 tail 后;空队列同时设置 head。
  3. 出队取 head 并前移;删掉最后一个节点时同步清空 tail。

代码实现

class LinkedQueue {
    private static class Node {
        int value;
        Node next;

        Node(int value) {
            this.value = value;
        }
    }

    private Node head;
    private Node tail;

    public void offer(int value) {
        Node node = new Node(value);

        if (tail == null) {
            head = node;
        } else {
            tail.next = node;
        }

        tail = node;
    }

    public Integer poll() {
        if (head == null) {
            return null;
        }

        int value = head.value;

        head = head.next;

        if (head == null) {
            tail = null;
        }

        return value;
    }

    public Integer peek() {
        return head == null ? null : head.value;
    }

    public boolean isEmpty() {
        return head == null;
    }
}
type queueNode struct {
    value int
    next  *queueNode
}
type LinkedQueue struct{ head, tail *queueNode }

func (q *LinkedQueue) Offer(value int) {
    node := &queueNode{value: value}
    if q.tail == nil {
        q.head = node
    } else {
        q.tail.next = node
    }
    q.tail = node
}

func (q *LinkedQueue) Poll() (int, bool) {
    if q.head == nil {
        return 0, false
    }
    value := q.head.value
    q.head = q.head.next
    if q.head == nil {
        q.tail = nil
    }
    return value, true
}

func (q *LinkedQueue) Peek() (int, bool) {
    if q.head == nil {
        return 0, false
    }
    return q.head.value, true
}

func (q *LinkedQueue) IsEmpty() bool {
    return q.head == nil
}

复杂度分析

  • 时间复杂度:每个操作时间 $O(1)$。
  • 空间复杂度:存储n个元素占 $O(n)$ 空间。

关键点总结

[!green]

head 指向最早入队元素,tail 指向最后入队元素;空队列两个指针都为空,才能保证后续重新入队正确。

易错点总结

[!yellow]

最后一个节点出队后必须清空tail,否则下一次入队可能接到失效链表上。

相似题目

题目 难度 关联与区别
707. 设计链表 中等 链式队列的入队、出队分别是尾插和删除头节点,可直接复用链表连接操作;额外保存尾指针即可省去入队时的定位遍历。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/35557140
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!