=【力扣-19. 删除链表的倒数第n个节点 ✨】Python笔记

0 阅读4分钟

链表中的“神行太保”:如何优雅地删除倒数第N个节点(只遍历一次!)

摘要:本文详解LeetCode 19题“删除链表的倒数第N个节点”。通过“双指针”技巧和“虚拟头节点”的巧妙配合,只需一次遍历即可解决问题,带你彻底搞懂链表操作中的边界处理艺术。


📚 核心知识点:双指针与虚拟头节点

在处理链表问题时,我们经常会遇到两个痛点:

  1. 不知道链表总长度:无法直接通过下标定位倒数第N个节点。
  2. 删除头节点的麻烦:如果要删除的是链表的第一个节点,普通的遍历逻辑容易失效。

为了解决这两个问题,我们需要掌握两个神器:

  1. 双指针(快慢指针)
    想象两个跑步的人,快指针(Fast) 先跑 N 步,然后慢指针(Slow) 才开始跑。当快指针跑到终点时,慢指针和快指针之间正好相差 N 步,此时慢指针的位置就是我们要找的“倒数第 N 个节点”的前驱位置。
  2. 虚拟头节点(Dummy Node)
    在链表头部前加一个“替身”节点,指向真正的头节点。这样无论我们要删除的是中间节点还是头节点,操作逻辑都是一致的(都是删除 slow.next),避免了复杂的边界判断。

📝 题目解析:LeetCode 19. 删除链表的倒数第 N 个结点

题目描述
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
(解释:倒数第2个节点是 4,删除后链表变为 1->2->3->5


💡 解题思路:拉开距离,同步前行

步骤演示

  1. 建立虚拟头节点dummy -> 1 -> 2 -> 3 -> 4 -> 5
  2. 双指针就位fastslow 都指向 dummy
  3. 拉开距离:让 fast 先走 N 步。此时 fast 领先 slow N 个节点。
  4. 同步前行fastslow 同时向后移动,直到 fast 到达链表末尾(即 fast.next 为空)。
  5. 锁定目标:此时 slow 刚好停在倒数第 N 个节点的前一个位置
  6. 执行删除slow.next = slow.next.next

💻 代码实现(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]:
        # 1. 创建虚拟头节点,指向head,解决删除头节点的边界问题
        dummy = ListNode(0, head)

        # 2. 初始化快慢指针,都指向虚拟头节点
        fast = dummy
        slow = dummy

        # 3. 快指针先走 n 步
        # 这样快慢指针之间就拉开了 n 的距离
        for _ in range(n):
            fast = fast.next

        # 4. 快慢指针同时移动,直到快指针到达链表尾部
        # 注意:判断条件是 fast.next,这样 slow 会停在“待删除节点”的前一个位置
        while fast.next:
            slow = slow.next
            fast = fast.next

        # 5. 执行删除操作
        # 此时 slow.next 就是要删除的节点
        slow.next = slow.next.next

        # 6. 返回真正的头节点(dummy.next)
        return dummy.next

🔍 深度图解:为什么要停在“前一个”位置?

假设链表是 1 -> 2 -> 3 -> 4 -> 5,我们要删除倒数第 2 个节点(即 4)。

  1. 初始状态dummy -> 1 -> 2 -> 3 -> 4 -> 5fastslow 都在 dummy

  2. 快指针先行n=2fast 走两步,指向 2

  3. 同步移动

    • 第一轮:fast3slow1
    • 第二轮:fast4slow2
    • 第三轮:fast5slow3
  4. 终止条件:此时 fast.next 为空,循环结束。

  5. 结果slow 停在 3slow.next4。执行 slow.next = slow.next.next,即 3 直接指向 5,成功删除 4


📌 总结与拓展

  1. 为什么要用 Dummy Node?
    如果链表只有一个节点 [1],且要删除倒数第 1 个节点。如果没有 dummyslow 指针很难处理“删除头节点”的情况(因为头节点没有前驱)。用了 dummy,逻辑统一为“删除 slow 的下一个节点”,代码更健壮。
  2. 空间复杂度O(1)O(1),只使用了常数级别的额外空间。

这道题是“双指针”技巧的经典应用,掌握了这个“拉开距离法”,以后遇到“查找倒数第 K 个节点”或者“寻找链表中点”的问题都能迎刃而解!


爱摸鱼的打工仔:关注我,每天分享一个提升效率的算法小技巧!🐟