19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点
mid(简单)
快慢指针
时间复杂度O(L) 空间复杂度O(1)

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 创建一个哑节点,其next指向head,这样可以简化删除头节点的情况
        ListNode dummy = new ListNode(0, head);
        // first指针从head开始
        ListNode first = head;
        // second指针从dummy开始
        ListNode second = dummy;
        
        // 先将first指针向前移动n步
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        
        // 然后first和second指针同时向前移动,直到first到达链表末尾
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        
        // 此时second指向要删除节点的前一个节点,删除节点
        second.next = second.next.next;
        
        // 返回新的头节点
        ListNode ans = dummy.next;
        return ans;
    }
}

错误代码:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        ListNode fast = head;
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        ListNode slow = head;
        ListNode pre = dummy;
        while(fast != null && fast.next != null) {
            fast = fast.next;
            slow = slow.next;
            pre = pre.next;
        }
        pre.next = slow.next;
        return dummy.next;
    }
}

代码在处理删除倒数第 n 个节点的逻辑上有一个小错误。
具体来说,在移动 fast 指针时,没有考虑到 fast 指针在链表末尾的情况。
此外,在移动 slow 和 pre 指针时,也应该从 dummy 节点开始,而不是从 head 节点开始。
假设我们有一个链表 1 -> 2 -> 3 -> 4 -> 5,并且我们想要删除倒数第 2 个节点,即节点 4。下面是原始错误代码的可视化过程:

  1. 初始化
    • 创建一个 dummy 节点,其 next 指向 head
    • fastslow 指针初始时都指向 headpre 指向 dummy
dummy -> 1 -> 2 -> 3 -> 4 -> 5
  pre    slow
         fast
  1. 移动 fast 指针 n
    • fast 指针移动 2 步,指向节点 3
dummy -> 1 -> 2 -> 3 -> 4 -> 5
  pre    slow
                fast
  1. 同时移动 fastslowpre
    • 由于 fast 指针的初始位置错误,fastslow 指针同时移动直到 fast 指向 null 时,slow 指针并没有停在要删除节点的前一个节点,而是停在了要删除的节点上。
dummy -> 1 -> 2 -> 3 -> 4 -> 5
                pre    slow
                           fast
  1. 删除节点
    • 根据错误代码的逻辑,pre.next = slow.next; 实际上会删除 slow 指向的节点的下一个节点,即节点 5,而不是我们想要删除的节点 4

正确的逻辑是让 fast 指针先移动 n+1 步,然后同时移动 fastslow 指针,直到 fast 指向 null。这样,slow 指针会停在要删除节点的前一个节点上,从而可以正确删除目标节点。

正确的过程如下:

  1. 初始化
    • fastslow 指针初始时都指向 dummy
dummy -> 1 -> 2 -> 3 -> 4 -> 5
  pre    slow
  fast
  1. 移动 fast 指针 n+1
    • fast 指针移动 3 步,指向节点 4
dummy -> 1 -> 2 -> 3 -> 4 -> 5
  pre    slow
                      fast
  1. 同时移动 fastslow
    • fastslow 指针同时移动直到 fast 指向 null,此时 slow 指向要删除节点的前一个节点,即节点 3
dummy -> 1 -> 2 -> 3 -> 4 -> 5
                pre    slow
                                fast
  1. 删除节点
    • slow.next = slow.next.next; 正确删除了节点 4
posted @ 2024-07-10 18:39  economies  阅读(7)  评论(0编辑  收藏  举报