Loading......

文章背景图

算法 - 链表删除专项

2026-04-20
11
-
- 分钟
|

核心:删除链表节点的两种姿势——普通删除需要 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.nextcur.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 指向 headcur 从 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 指向 headcurhead 同时遍历

  • 如果 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 - 中等)

给你两个链表 list1list2,以及两个整数 ab。将 list1 中下标从 ab 的全部节点删除,并将 list2 接在被删除节点的位置。返回结果链表的头指针。

示例

输入:list1 = [10,1,13,6,9,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
输出:[10,1,13,1000000,1000001,1000002,5]

解题思路

  1. 找到 a-1 位置的节点 preA(被删除段的前一个节点)

  2. 找到 b+1 位置的节点 postB(被删除段的后一个节点)

  3. 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

三、链表删除算法通用解题总结 ✨

核心技巧

技巧

适用场景

值复制 + 删除下一个

只知道待删除节点本身,且它不是尾节点

哨兵节点 dummy

可能需要删除头节点时,加 dummy 统一逻辑

双指针间距法

删除倒数第 N 个节点,保持间距为 N

先反转再处理

需要从链表尾部往左处理时(2487 题)

set 预处理

需要快速判断值是否存在时(3217 题)

何时需要哨兵节点?

  • 删除头节点的情况(n 等于链表长度、重复节点在开头)

  • 不确定头节点是否会被删除时,加 dummy 保险

复杂度速记

问题类型

时间

空间

删除单个节点

O(1)

O(1)

删除倒数第 N 个

O(n)

O(1)

删除重复 I

O(n)

O(1)

删除重复 II

O(n)

O(1)

移除链表元素

O(n)

O(1)

合并两个链表

O(n+m)

O(1)

评论交流