链表操作一直是算法面试中的高频考点,而删除倒数第N个节点这道题更是经典中的经典。题目要求我们给定一个链表,删除链表中倒数第n个节点,并返回链表的头节点。
单向链表的数据结构决定了我们只能从头节点开始依次向后遍历,无法像数组那样通过索引直接访问任意位置的元素。这就带来了几个关键难点:
在实际编码中,有几个边界条件需要特别注意:
提示:在实际面试中,即使题目说明输入有效,主动讨论边界条件也能展示你的严谨性。
针对这个问题,通常有三种主流解法:
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 计算长度法 | O(2L) | O(1) | 直观但需要两次遍历 |
| 栈辅助法 | O(L) | O(L) | 逻辑简单但空间开销大 |
| 双指针法 | O(L) | O(1) | 最优解,一次遍历 |
双指针法(快慢指针)之所以成为最优解,主要因为:
在实现双指针法前,我们先解决边界条件问题。哨兵节点(Dummy Node)是链表问题中的常用技巧:
java复制ListNode dummy = new ListNode(0, head);
哨兵节点的作用:
双指针法的关键在于维护两个指针之间的固定距离:
以链表1->2->3->4->5,n=2为例:
code复制初始状态:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null
fast = dummy
slow = dummy
快指针先走3步(n+1=3):
fast: dummy -> 1 -> 2 -> 3
slow: dummy
同步移动:
fast: 3 -> 4 -> 5 -> null
slow: dummy -> 1 -> 2 -> 3
最终位置:
fast = null
slow指向节点3(倒数第3个节点)
删除操作:
3.next = 3.next.next (即4.next=5)
java复制public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
// 快指针先走n+1步
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
// 同步移动直到快指针到末尾
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 执行删除
slow.next = slow.next.next;
return dummy.next;
}
空指针异常:
删除错误节点:
内存泄漏:
打印指针位置:
java复制System.out.println("Fast at: " + (fast != null ? fast.val : "null"));
System.out.println("Slow at: " + (slow != null ? slow.val : "null"));
可视化调试:
边界测试:
链表中点查找:
环形链表检测:
两个链表的交点:
在实际工程中,可以进一步考虑:
在实际刷题和面试中,我发现以下几点特别重要:
这道题看似简单,但涵盖了链表操作的多个核心技巧。我在第一次遇到时花了2小时才完全理解,但现在能在5分钟内写出无bug的实现。这证明算法能力确实可以通过刻意练习提升。