19. 删除链表的倒数第 N 个结点
19. 删除链表的倒数第 N 个结点
mid(简单)
快慢指针
时间复杂度O(L) 空间复杂度O(1)
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个哑节点,其next指向head,这样可以简化删除头节点的情况
ListNode dummy = new ListNode(0, head);
// first指针从head开始
ListNode first = head;
// second指针从dummy开始
ListNode second = dummy;
// 先将first指针向前移动n步
for (int i = 0; i < n; ++i) {
first = first.next;
}
// 然后first和second指针同时向前移动,直到first到达链表末尾
while (first != null) {
first = first.next;
second = second.next;
}
// 此时second指向要删除节点的前一个节点,删除节点
second.next = second.next.next;
// 返回新的头节点
ListNode ans = dummy.next;
return ans;
}
}
错误代码:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode();
dummy.next = head;
ListNode fast = head;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
ListNode slow = head;
ListNode pre = dummy;
while(fast != null && fast.next != null) {
fast = fast.next;
slow = slow.next;
pre = pre.next;
}
pre.next = slow.next;
return dummy.next;
}
}
代码在处理删除倒数第 n 个节点的逻辑上有一个小错误。
具体来说,在移动 fast 指针时,没有考虑到 fast 指针在链表末尾的情况。
此外,在移动 slow 和 pre 指针时,也应该从 dummy 节点开始,而不是从 head 节点开始。
假设我们有一个链表 1 -> 2 -> 3 -> 4 -> 5
,并且我们想要删除倒数第 2
个节点,即节点 4
。下面是原始错误代码的可视化过程:
- 初始化:
- 创建一个
dummy
节点,其next
指向head
。 fast
和slow
指针初始时都指向head
,pre
指向dummy
。
- 创建一个
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 移动
fast
指针n
步:fast
指针移动2
步,指向节点3
。
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 同时移动
fast
、slow
和pre
:- 由于
fast
指针的初始位置错误,fast
和slow
指针同时移动直到fast
指向null
时,slow
指针并没有停在要删除节点的前一个节点,而是停在了要删除的节点上。
- 由于
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 删除节点:
- 根据错误代码的逻辑,
pre.next = slow.next;
实际上会删除slow
指向的节点的下一个节点,即节点5
,而不是我们想要删除的节点4
。
- 根据错误代码的逻辑,
正确的逻辑是让 fast
指针先移动 n+1
步,然后同时移动 fast
和 slow
指针,直到 fast
指向 null
。这样,slow
指针会停在要删除节点的前一个节点上,从而可以正确删除目标节点。
正确的过程如下:
- 初始化:
fast
和slow
指针初始时都指向dummy
。
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 移动
fast
指针n+1
步:fast
指针移动3
步,指向节点4
。
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 同时移动
fast
和slow
:fast
和slow
指针同时移动直到fast
指向null
,此时slow
指向要删除节点的前一个节点,即节点3
。
dummy -> 1 -> 2 -> 3 -> 4 -> 5
pre slow
fast
- 删除节点:
slow.next = slow.next.next;
正确删除了节点4
。