本文详解如何在单向链表中实现“跳过 m 个节点、随后删除 n 个节点”的周期性操作,涵盖正确逻辑设计、边界条件防护、实例代码及常见陷阱规避。
本文详解如何在单向链表中实现“跳过 m 个节点、随后删除 n 个节点”的周期性操作,涵盖正确逻辑设计、边界条件防护、实例代码及常见陷阱规避。
在链表算法题中,“Skip M, Delete N” 是一类典型的原地遍历+结构修改问题:需从头开始,每轮先安全跳过 M 个有效节点(保留),再移除紧随其后的 N 个节点,重复直至链表尾部。看似简单,但极易因指针误操作、边界未校验或静态/实例混用导致 NullPointerException 或逻辑死循环——正如原始代码在 temp=1; 强制重置循环变量后引发无限遍历,最终访问 null.next 报错。
✅ 正确解法核心思路
- 状态归属清晰:链表应作为实例对象管理自身 head 和 tail,所有操作方法(addFirst、print、skipMdeleteN)均为非静态成员方法,避免静态变量污染与多实例冲突;
- 循环终止可靠:使用 while (curr != null) 主循环,内层跳过与删除均需实时检查 curr == null,防止越界;
- 删除逻辑简洁健壮:不嵌套多层 for 循环重置变量;而是用两个独立的 for 完成「定位跳过终点」和「定位删除终点」,最后通过 ptr.next = curr 一次性断链;
-
边界全覆盖:
- 若 n ≤ 0 或链表为空 → 直接返回;
- 若 m == 0 → 全删,head = null;
- 任意阶段 curr 变为 null → 立即退出,无需补删。
? 完整可运行代码(Java)
public class LinkedList {
public class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
public Node head;
public Node tail;
public void addFirst(int data) {
Node newNode = new Node(data);
newNode.next = head;
head = newNode;
if (tail == null) tail = head;
}
public void print() {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + "->");
temp = temp.next;
}
System.out.println("null");
}
public void skipMdeleteN(int m, int n) {
// 边界防护:无删除意义或空链表
if (n <= 0 || head == null) return;
// m == 0:全部删除
if (m == 0) {
head = null;
return;
}
Node curr = head;
while (curr != null) {
// Step 1: 跳过 m 个节点(保留这 m 个)
for (int i = 1; i < m && curr != null; i++) {
curr = curr.next;
}
if (curr == null || curr.next == null) break; // 无法继续跳过或无后续可删
// Step 2: ptr 指向第 m 个节点(保留区终点),准备断链
Node ptr = curr;
// Step 3: 向前推进 n+1 步:跳过 n 个待删节点,到达「删除区之后第一个保留节点」
for (int i = 0; i <= n && curr != null; i++) {
curr = curr.next;
}
// Step 4: 断链 —— ptr.next 指向 curr(即跳过中间 n 个节点)
ptr.next = curr;
}
}
public static void main(String[] args) {
LinkedList ll = new LinkedList();
// 构建链表:1->2->3->4->5->6->7->8->null
ll.addFirst(8);
ll.addFirst(7);
ll.addFirst(6);
ll.addFirst(5);
ll.addFirst(4);
ll.addFirst(3);
ll.addFirst(2);
ll.addFirst(1);
System.out.print("Original: ");
ll.print(); // 输出:1->2->3->4->5->6->7->8->null
int m = 2, n = 2;
ll.skipMdeleteN(m, n);
System.out.print("After skip " + m + ", delete " + n + ": ");
ll.print(); // 输出:1->2->5->6->null
}
}⚠️ 关键注意事项
- 不要手动重置循环变量(如原始代码中的 temp = 1;):这会破坏 for 循环自然控制流,导致不可预测的迭代行为;
- Node 类必须是非静态内部类:否则无法访问外部类的实例字段(如 head),且违背面向对象封装原则;
-
删除时的步数计算要精确:
- 跳过 m 个 → 需走 m−1 步(起点已算第 1 个);
- 删除 n 个 → 需从 ptr 出发,将 curr 推进 n+1 步(ptr.next 到 curr 之间恰好 n 个节点被跳过);
- 始终优先检查 curr != null:尤其在 curr = curr.next 前后,任何一步为 null 都应终止当前轮次。
✅ 总结
该问题本质是链表上的有限状态机遍历:每个周期包含两个确定动作(跳 & 删),关键在于用清晰的指针语义替代易错的计数器重置,并以防御式编程覆盖所有边缘场景。掌握此模式后,可轻松拓展至类似需求,如“每 K 个反转”、“隔 N 个插入”等变体。务必坚持实例化设计、即时空值校验、单次断链操作——这是写出鲁棒链表代码的三大基石。










