【LeetCode 19】力扣算法:删除链表的倒数第 N 个结点 —— 快慢指针法

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

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

image


核心思想:

要删除链表的倒数第 n 个节点,我们可以使用双指针法。这种方法的关键在于使用两个指针,一个快指针先走 n 步,然后两个指针一起走,当快指针到达链表末尾时,慢指针就恰好指向了需要删除的节点的前一个节点。

算法步骤:

  1. 初始化:创建虚拟头结点;创建两个指针 fast 和 slow,初始值为虚拟头结点。

  2. 快指针先行:让 fast 指针先前进 n 步。如果 fast.next 为 null,说明要删除的是头节点,直接处理这种情况。

  3. 双指针前进:同时移动 fast 和 slow 指针,直到 fast 到达链表末尾。

  4. 删除节点:此时 slow 指向的就是需要删除的节点的前一个节点,调整 slow.next 指向 slow.next.next,从而删除了倒数第 n 个节点。

  5. 返回结果:返回处理后的链表头节点。

复杂度分析:

  • 时间复杂度:O(sz),其中 sz 是链表的长度。我们最多遍历链表两次。

  • 空间复杂度:O(1),我们只使用了两个指针,所以额外空间复杂度是常数级别的。

做这个题目时,一开始我犯了个错误,我不使用虚拟头结点,直接让快慢指针指向head的头结点,然后按步骤删除,结果是报错。我想了很久,这个方法明明理论上是没有问题的,为什么会报错呢?最后我发现我把快慢指针初始为head的头结点的做法,不能处理当删除值是头结点的情况。当测试用例要删除的结点是头结点时,会直接报错,因为没有处理这种特殊情况。

所以最好且最方便的做法就是创建虚拟头结点,让快慢指针初始值为虚拟头结点,才能防止特殊情况。

正确的 Java 代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 创建虚拟头结点,防止需要删除的是头结点的情况
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode slow = dummyHead;
        ListNode fast = dummyHead;
        
        // 步骤一:先让快指针走n步
        for(int i=0; i<n; i++){
            fast = fast.next;
        } 
        // 步骤二:快慢指针同时走,直到快指针到达最后一个结点
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        // 步骤三:删除慢指针的后一个结点
        slow.next = slow.next.next;
        
        return dummyHead.next;
    }
}
posted @ 2025-07-29 16:21  junjunyi  阅读(19)  评论(0)    收藏  举报