链表模板:虚拟头结点、快慢指针与原地翻转的标准套路#
链表题看似变化多,其实核心套路很稳定。只要把几个标准动作写熟,绝大多数链表题都能拆回基础操作。
⚡ 速记版模板#
// 模板用途:链表基础操作骨架(虚拟头 + 反转指针)
ListNode dummy = new ListNode(0); // 虚拟头结点:统一处理头节点变化(删除/插入场景常用)
dummy.next = head; // 连接原链表头
ListNode previous = null; // 反转链表时,指向 current 的前驱节点
ListNode current = head; // 当前正在处理的节点
while (current != null) { // 逐个节点进行指针翻转
ListNode next = current.next; // 先保存后继,防止断链
current.next = previous; // 反转当前指针方向
previous = current; // previous 前移到当前节点
current = next; // current 前移到原后继节点
}
return previous; // 循环结束时 previous 指向新链表头java🎯 链表题常见核心动作#
- 虚拟头结点
- 快慢指针
- 原地反转
- 合并两个有序链表
- 分治或堆合并多条链表
Hot 100 里的典型题目:
💡 模板一:虚拟头结点#
适合:
- 删除节点
- 插入节点
- 合并链表
- 统一处理头节点变化
class Solution {
public ListNode removeNode(ListNode head) {
ListNode dummy = new ListNode(0); // 虚拟头,避免删除头节点时特殊处理
dummy.next = head; // 指向原头节点
ListNode current = dummy; // 从 dummy 开始遍历,便于操作 current.next
while (current.next != null) { // 只要后继存在就可判断是否删除
if (shouldDelete(current.next)) { // 判断后继节点是否满足删除条件
current.next = current.next.next; // 删除后继:跳过该节点
} else {
current = current.next; // 不删则正常前进
}
}
return dummy.next; // 返回可能变化后的新头节点
}
private boolean shouldDelete(ListNode node) {
return false; // 模板占位:按题意实现删除条件
}
}java💡 模板二:反转链表#
class Solution {
public ListNode reverseList(ListNode head) {
ListNode previous = null; // 反转后 current 应指向的前驱
ListNode current = head; // 遍历指针
while (current != null) { // 遍历原链表
ListNode next = current.next; // 保存后继,防止链表丢失
current.next = previous; // 翻转当前指针
previous = current; // previous 前进
current = next; // current 前进
}
return previous; // 新头节点
}
}java💡 模板三:快慢指针找中点 / 判环#
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head; // 慢指针每次走一步
ListNode fast = head; // 快指针每次走两步
while (fast != null && fast.next != null) { // fast 能走两步才继续
slow = slow.next; // 慢指针走一步
fast = fast.next.next; // 快指针走两步
}
return slow; // fast 到尾时,slow 位于中点(偶数长度时是后中点)
}
}java💡 模板四:合并两个有序链表#
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(0); // 虚拟头节点
ListNode tail = dummy; // 已合并链表的尾指针
while (list1 != null && list2 != null) { // 两条链表都未走完时,持续选较小节点
if (list1.val <= list2.val) { // 选 list1 当前节点
tail.next = list1;
list1 = list1.next; // list1 前进
} else {
tail.next = list2; // 选 list2 当前节点
list2 = list2.next; // list2 前进
}
tail = tail.next; // 尾指针始终跟到最后
}
tail.next = list1 != null ? list1 : list2; // 拼接剩余未处理部分
return dummy.next; // 返回合并后头节点
}
}java💡 模板五:删除倒数第 K 个节点#
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0); // 虚拟头,统一处理删除头节点情况
dummy.next = head;
ListNode fast = dummy; // 快指针先走 n 步
ListNode slow = dummy; // 慢指针随后与 fast 保持 n 间距
for (int step = 0; step < n; step++) { // 先拉开 n 个节点距离
fast = fast.next;
}
while (fast.next != null) { // 一起移动直到 fast 到尾节点
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next; // slow 恰好位于待删节点前一个,执行删除
return dummy.next; // 返回新头节点
}
}java⚠️ 易错点#
-
忘记用虚拟头结点
- 一涉及删除头节点,
dummy几乎总能让代码更稳
- 一涉及删除头节点,
-
反转时断链
- 一定先保存
next
- 一定先保存
-
快慢指针初始位置不统一
- 不同题目的“中点定义”会略有不同
-
删除倒数第 K 个节点时偏移量错 1
- 用
dummy起步最稳
- 用
🎨 面试时怎么说#
链表题本质上就是指针重连问题,我会优先考虑是否需要虚拟头结点、是否适合快慢指针、是否要做局部或整体反转。
📌 一句话总结#
链表题别被题面吓住,绝大多数都能拆成:
- 找位置
- 断开
- 反转
- 连接