1. 题目描述

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

2. 解题思路

2.1 方法一:两次遍历算法

思路

该问题可以简化:删除从列表开头数起的第 (L - n + 1) 个结点,其中 L 是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。

算法

  1. 首先我们将添加一个 哑结点哨兵节点)作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部
  2. 第一次遍历,我们找出列表的长度 L
  3. 第二次遍历,找到要删除的节点,即第 (L - n) 个结点,删除即可。

但是题目要求一次遍历。

2.2 方法二:一次遍历算法

设置前后两个指针,front 和 back,他们之间的间隔是固定的,保持为 n,而后同步往后移动。当 back 指针指向 尾结点之后的 None 时,front 节点刚好指向需要删除节点的前一个节点 ,而后删除即可。详见下面动画。(动画中 p 即 front,q即 back)
参考:动画图解 LeetCode 第 19 号问题:删除链表的倒数第 N 个节点.

2.3 Python代码详解

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        s = ListNode(None)  # 增加哨兵节点,便于统一对头结点操作 (删除头结点比较方便)
        s.next = head  

        front = s           # 前后两个指针,当 back 指针指向 尾结点之后的 None 时,front 节点刚好指向需要删除节点的前一个节点 
        back = s
        for k in range(0, n+1):  # 移动 back 指针,使得 front 和back 指针间隔 n 个节点的距离
            back = back.next

        # print(front, back) # 善于使用 print 进行调试 
        
        while (back):            # 两个指针同步后移
            front = front.next
            back = back.next
        
        front.next = front.next.next   # 删除节点操作
        print(front)
        return s.next       # 不能返回 head 因为可能头结点都删除掉了!!!

3. 举一反三

关于双指针的用法还有一些经典题目。详见之前的文章如下链接:

【LeetCode】141 环形链表 I,142. 环形链表 II(双指针 中学追及问题)

【LeetCode 】160. 相交链表(双指针法 Python 7行代码 中学追及问题)

【LeetCode 】234. 回文链表(史上最详细 图文并茂 双指针法)

【LeetCode 】876. 链表的中间结点(快慢 双指针法 追及问题系列)