

核心思路:制造"距离差"
要删除倒数第 nnn 个节点,需要找到它的前驱节点(即倒数第 n+1n+1n+1 个)。
- 让 fast 指针先向前走 n+1n+1n+1 步。此时,fast (指向第n+1个节点)和 slow (指向第一个节点)之间隔了 nnn 个节点。
- 同时移动 fast 和 slow,直到 fast 指向最后一个节点。此时 slow 恰好指向倒数第 n+1n+1n+1 个节点
这道题不需要创建新链表,所以不是:
python
prehead = ListNode(-1)
dummy = prehead
... ...
# 建立新节点并移动指针
dummy.next = ... ...
dummy = dummy.next
重组旧链表,只需要新建一个 prehead 节点,指向链表
python
prehead = ListNode(-1)
prehead.next = head
... ...
return prehead.next
直接返回head不行吗,为什么要prehead = ListNode(-1)
prehead.next = head 返回prehead.next:
核心原因:头节点也是会被删除的
假设链表是 [1, 2],要删除倒数第 2 个(也就是删除 1):
- prehead 指向 1。
- 经过计算,slow 停在 prehead 上。
- 执行 slow.next = slow.next.next(即 prehead.next = 2)。
- 此时:head 变量依然指向 1。prehead.next 指向 2。
如果 return head,结果是 [1, 2](错误); 如果 return prehead.next,结果是 [2](正确)
"哑节点" (Dummy Node) 的作用
- 防止"丢头":如上所述,处理头节点被删除的情况。、、
- 空链表保护:当链表被删空时,prehead.next 会正确返回 None,而直接操作 head 可能会导致空指针引用报错
时间复杂度 :O(N)O(N)O(N)
虽然有两个循环(for 和 while),但指针一共只从头走到尾一次。
空间复杂度:O(1)O(1)O(1)
只额外申请了 prehead, fast, slow 这几个引用空间。
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 用于指向新链表
prehead = ListNode(-1)
prehead.next = head # 这一步是修复报错的关键
# 用于操作旧链表
fast = slow = prehead
# fast先走n+1步
for i in range(n+1):
fast = fast.next
# 同时移动 fast 和 slow,直到 fast 指向最后一个节点
# 此时 slow 恰好指向倒数第 n+1 个节点
while fast:
slow = slow.next
fast = fast.next
# 倒数第 n+1 个节点,应该指向:倒数第 n+2 个节点
#(跳过倒数n)
tmp = slow.next.next
slow.next = tmp
return prehead.next