核心:删除链表节点的两种姿势——普通删除需要 prev 节点,聪明做法是值复制 + 删除下一个节点。哨兵节点(dummy)用于处理可能删除头节点的场景。
一、基础知识点
1. 删除单个节点(LeetCode 237)💡
题目描述
给定一个要删除的节点,由于单链表只能从前一个节点找到后一个节点,removeNodFromLast 这类题通常无法直接获取 prev。但如果题目保证待删除节点不是链表的最后一个节点,可以用这个技巧。
核心思路
脑筋急转弯:无法直接删除,就 "复制 + 删除下一个"
将下一个节点的值复制到当前节点,然后删除下一个节点
效果等同于删除了当前节点
复杂度
时间:O(1)
空间:O(1)
Python 实现
def deleteNode(node):
# 题目保证 node 不是最后一个节点
node.val = node.next.val # 1. 把下一个节点的值复制过来
node.next = node.next.next # 2. 删除下一个节点(跳过它)
2. 删除倒数第 N 个节点(LeetCode 19)📐
题目描述
给你链表的头节点 head 和整数 n,删除链表的倒数第 n 个节点,返回头节点。
何时需要哨兵节点? 如果 n 等于链表长度,头节点会被删除,此时需要 dummy。
解法一:先求长度
遍历链表得到长度
len倒数第
n个 → 正数第len - n + 1个再遍历到它的上一个节点,执行删除
解法二:双指针(推荐)✨
要删除倒数第
N个节点,需要找到倒数第 N+1 个节点初始化右指针指向 dummy,先让右指针走
N+1步然后左右指针一起走,当右指针走到最后一个节点,左指针就指向倒数第 N+1 个节点
核心思路
利用双指针间距保持为
N的性质当右指针到达末尾,左指针恰好在目标位置的前一个
Python 实现
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(next=head) # 哨兵节点:处理可能删除头节点的情况
right = dummy
for _ in range(n + 1): # 右指针先走 n+1 步(走到第 n+1 个节点)
right = right.next
left = dummy # 左指针从 dummy 开始
while right: # 左右指针一起走,直到右指针到末尾
left = left.next
right = right.next
left.next = left.next.next # 删除倒数第 n 个节点
return dummy.next
复杂度
时间:O(n)(两个指针各遍历一次)
空间:O(1)
3. 删除重复节点 I(只保留一个)(LeetCode 83)🎯
题目描述
存在一个按升序排列的链表,删除重复元素,使每个元素只出现一次。
核心思路
不需要 dummy:因为我们保留头节点,头节点不会被删
从头节点开始,遍历链表
如果当前节点值等于下一个节点值,删除下一个节点;否则移动到下一个节点
Python 实现
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head: # 特判空链表
return head
cur = head
while cur.next: # 还有下一个节点就继续
if cur.val == cur.next.val: # 值相同,删除下一个节点
cur.next = cur.next.next
else: # 值不同,移动到下一个节点
cur = cur.next
return head
复杂度
时间:O(n)
空间:O(1)
4. 删除重复节点 II(全部删除)(LeetCode 82)🎯
题目描述
存在一个按升序排列的链表,删除所有重复的元素,使每个元素只出现一次(重复的节点一个不留)。
核心思路
需要 dummy:如果开头有重复节点,头节点会被删除
用
cur遍历,每次看cur.next和cur.next.next如果这两个节点值相同,说明有重复,用循环不断删除直到值不同或链表结束
如果不同,
cur正常移动
Python 实现
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(next=head) # 哨兵节点:处理可能删除头节点的情况
cur = dummy
while cur.next and cur.next.next:
if cur.next.val == cur.next.next.val: # 发现重复
val = cur.next.val # 记录待删除的值
while cur.next and cur.next.val == val:
cur.next = cur.next.next # 删除所有值等于 val 的节点
else:
cur = cur.next # 值不同,正常移动
return dummy.next
复杂度
时间:O(n)(每个节点最多被访问一次)
空间:O(1)
二、练习(4道链表删除题)
1. 203. 移除链表元素 🔢
题目描述(LeetCode 203 - 简单)
给你链表的头节点 head 和一个整数 val,删除链表中所有满足 Node.val == val 的节点,返回新的头节点。
示例
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
解题思路
初始化
dummy指向head,cur从 dummy 开始如果
cur.next.val == val,删除下一个节点;否则cur右移最终返回
dummy.next
复杂度
时间:O(n)
空间:O(1)
Python 代码
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
dummy = ListNode(next=head) # 哨兵节点
cur = dummy
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next # 删除下一个节点
else:
cur = cur.next
return dummy.next
2. 3217. 从链表中移除在数组中存在的节点 🧩
题目描述(LeetCode 3217 - 中等)
给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后,返回修改后的链表的头节点。
示例
输入:nums = [1,2,3], head = [1,2,3,4,5]
输出:[4,5]
解释:移除数值为 1, 2 和 3 的节点。
解题思路
先将
nums存入set,查找 O(1)双指针:
dummy指向head,cur和head同时遍历如果
head.val在集合中,删除;否则cur右移
复杂度
时间:O(n + m)(n 为链表长度,m 为 nums 长度)
空间:O(m)(set 存储 nums)
Python 代码
class Solution:
def modifiedList(self, nums: List[int], head: Optional[ListNode]) -> Optional[ListNode]:
st = set(nums) # 集合查找 O(1)
dummy = cur = ListNode(next=head)
while head:
if head.val in st:
cur.next = cur.next.next # 在数组中,删除节点
head = head.next
else:
cur = cur.next
head = head.next
return dummy.next
3. 2487. 从链表中移除节点 📐
题目描述(LeetCode 2487 - 中等)
给你一个链表的头节点 head,移除每个右侧有一个更大数值的节点。返回修改后链表的头节点。
示例
输入:head = [5,2,13,3,8]
输出:[13,8]
解释:需要移除的节点是 5,2 和 3。
节点 13 在节点 5 右侧。
节点 13 在节点 2 右侧。
节点 8 在节点 3 右侧。
解题思路
从链表尾部往前的思路:先反转链表
遍历时维护当前最大值
curMax如果当前节点值 ≤
curMax,删除;如果 >curMax,更新curMax最后再反转回来
复杂度
时间:O(n)
空间:O(1)
Python 代码
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre, cur = None, head
while cur:
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
return pre
def removeNodes(self, head: Optional[ListNode]) -> Optional[ListNode]:
head = self.reverseList(head) # 反转后从左到右就是原链表的从右到左
cur = head
while cur.next:
if cur.val > cur.next.val: # 当前 ≤ 最大值,删除下一个
cur.next = cur.next.next
else: # 当前 > 最大值,保留并更新
cur = cur.next
return self.reverseList(head) # 恢复反转
4. 1669. 合并两个链表 🧩
题目描述(LeetCode 1669 - 中等)
给你两个链表 list1 和 list2,以及两个整数 a 和 b。将 list1 中下标从 a 到 b 的全部节点删除,并将 list2 接在被删除节点的位置。返回结果链表的头指针。
示例
输入:list1 = [10,1,13,6,9,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
输出:[10,1,13,1000000,1000001,1000002,5]
解题思路
找到
a-1位置的节点preA(被删除段的前一个节点)找到
b+1位置的节点postB(被删除段的后一个节点)将
preA.next = list2,再找到list2的尾节点,连接到postB
复杂度
时间:O(n + m)
空间:O(1)
Python 代码
class Solution:
def mergeInBetween(self, list1: ListNode, a: int, b: int, list2: ListNode) -> ListNode:
preA = list1
for _ in range(a - 1): # 1. 找到被删除段的前一个节点
preA = preA.next
postB = preA
for _ in range(b - a + 2): # 2. 找到被删除段的后一个节点
postB = postB.next
preA.next = list2 # 3. 拼接 list2
tail2 = list2
while tail2.next: # 4. 找到 list2 的尾节点
tail2 = tail2.next
tail2.next = postB # 5. 尾节点连接 postB
return list1
三、链表删除算法通用解题总结 ✨
核心技巧
何时需要哨兵节点?
删除头节点的情况(
n等于链表长度、重复节点在开头)不确定头节点是否会被删除时,加 dummy 保险