给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
进阶:你能尝试使用一趟扫描实现吗?
双指针法
这个算法用于 删除链表的倒数第 n
个节点,并返回删除后的链表头节点。核心思路是 快慢指针:
-
虚拟头节点(
pre_head
)-
创建一个虚拟头节点
pre_head
,它的next
指向真正的头节点head
。 -
这样可以避免单独处理头节点的删除(比如
n = 链表长度
时删除头节点)。
-
-
快慢指针初始化
-
fast
和slow
都从pre_head
开始。
-
-
快指针先走
n
步-
fast
先向前移动n
步,使得fast
和slow
之间相隔n
个节点。 -
这样当
fast
到达链表末尾时,slow
正好指向倒数第n+1
个节点(即要删除节点的前驱)。
-
-
快慢指针同步移动
-
同时移动
fast
和slow
,直到fast
到达最后一个节点(fast.next
为None
)。 -
此时
slow
指向倒数第n+1
个节点。
-
-
删除目标节点
-
slow.next = slow.next.next
,跳过倒数第n
个节点,完成删除。
-
-
返回新链表头
-
返回
pre_head.next
,因为pre_head
始终指向真正的头节点。
-
示例
假设链表为 1 → 2 → 3 → 4 → 5
,n = 2
(删除倒数第 2 个节点 4
):
-
初始化:
pre_head → 1 → 2 → 3 → 4 → 5
,fast = slow = pre_head
。 -
快指针先走 2 步:
-
fast
移动到1
(第 1 步)。 -
fast
移动到2
(第 2 步)。
-
-
快慢指针同步移动:
-
fast
移动到3
,slow
移动到1
。 -
fast
移动到4
,slow
移动到2
。 -
fast
移动到5
,slow
移动到3
(此时fast.next
为None
,停止)。
-
-
删除
slow.next
(即4
):-
slow.next = slow.next.next
(3.next
指向5
)。
-
-
最终链表:
1 → 2 → 3 → 5
。
时间复杂度
-
O(n),只需遍历链表一次。
空间复杂度
-
O(1),只用了几个指针变量,没有额外空间。
关键点
-
虚拟头节点 简化边界情况(如删除头节点)。
-
快指针先走
n
步,确保slow
最终指向倒数第n+1
个节点。 -
同步移动快慢指针,直到
fast
到达末尾。
这样就能高效删除倒数第 n
个节点! 🚀
# 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]:
pre_head = ListNode()
pre_head.next = head
fast = slow = pre_head
for _ in range(n):
fast = fast.next
while fast.next: # 直到fast到达尾节点停止
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return pre_head.next