
本文详解如何在单向链表中实现「跳过 m 个节点、删除后续 n 个节点」的循环操作,提供健壮、非静态、边界安全的 java 实现,并修正原始代码中的空指针异常、逻辑死循环及面向对象设计错误。
本文详解如何在单向链表中实现「跳过 m 个节点、删除后续 n 个节点」的循环操作,提供健壮、非静态、边界安全的 java 实现,并修正原始代码中的空指针异常、逻辑死循环及面向对象设计错误。
在链表处理类题目中,“Skip M, Delete N” 是一类典型的就地修改(in-place)遍历模式题。其核心逻辑是:从头开始,每跳过 M 个有效节点后,立即删除接下来的 N 个节点;重复该过程直至链表末尾。但若实现不当(如忽略空指针、混淆实例/静态成员、未处理边界),极易引发 NullPointerException 或无限循环——正如原始代码在 curr.next 处崩溃所示。
✅ 正确设计原则
- 面向对象合规:head、tail 和所有操作方法应为实例成员,避免 static 引用导致状态混乱;
- 边界防御优先:必须检查 curr == null、m == 0、n
- 逻辑清晰分段:将“跳过”与“删除”解耦,用独立循环控制,避免嵌套中重置计数器引发死循环(原代码 temp = 1; 是致命错误);
- 删除即断链:删除 N 个节点的本质是让跳过终点的 ptr.next 指向第 N+1 个节点(或 null)。
? 完整可运行实现
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");
}
/**
* 跳过 m 个节点,删除后续 n 个节点,循环至链表尾
* @param m 要跳过的节点数(>=0)
* @param n 要删除的节点数(>=0)
*/
public void skipMdeleteN(int m, int n) {
// 边界保护:空链表或无效 n 直接返回
if (n <= 0 || head == null) return;
// 特殊情况:m == 0 → 全部删除
if (m == 0) {
head = null;
return;
}
Node curr = head;
while (curr != null) {
// Step 1: 跳过 m-1 个节点(到达第 m 个节点)
// 注意:我们从 head 开始计为第 1 个,因此只需移动 m-1 次
for (int i = 1; i < m; i++) {
curr = curr.next;
if (curr == null) return; // 链表不足 m 个,终止
}
// Step 2: 保存跳过终点,准备删除
Node ptr = curr;
// Step 3: 向前推进 n+1 步:跳过 n 个待删节点,抵达第 (n+1) 个存活节点
for (int i = 0; i <= n; i++) {
curr = curr.next;
if (curr == null) break;
}
// 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
ll.skipMdeleteN(2, 2); // 跳2删2 → 保留1,2;删3,4;保留5,6;删7,8
System.out.print("After M=2,N=2: ");
ll.print(); // 输出: 1->2->5->6->null
}
}⚠️ 关键注意事项
- m 与 n 的语义:skipMdeleteN(2, 2) 表示「跳过前 2 个(1→2),再删除接下来 2 个(3→4)」,而非跳过索引为 2 的节点;
- 循环终止条件:外层 while (curr != null) 保证不会空指针;内层 for 循环中每次移动前均校验 curr == null;
- 删除逻辑本质:不是逐个 newNode.next = newNode.next.next,而是批量断链——通过一次 ptr.next = curr 移除连续 n 个节点,时间复杂度 O(L),空间 O(1);
- m == 0 的特殊处理:此时无节点可跳,直接清空链表(head = null),否则会陷入无限循环或异常;
-
测试建议:务必覆盖以下用例:
- m = 0, n = 3 → 全删;
- m = 5, n = 1(链表长度=4)→ 无删除,原样输出;
- n = 0 → 无操作;
- m = 1, n = 1 → 交错保留/删除(1→3→5…)。
掌握此模式不仅解决本题,更为实现链表分组翻转、间隔合并等进阶操作奠定基础。核心在于:以安全遍历为前提,以断链代替逐删,以实例封装保障可重用性。










