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

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

class Solution {
public:
  ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *Eager = head;
    ListNode *Lazy = head;

    // Eager first
    for (int i = 0; i < n; ++i)
      Eager = Eager->next;

    ListNode *Prev = nullptr;
    // Then both
    while (Eager) {
      Prev = Lazy;
      Lazy = Lazy->next;
      Eager = Eager->next;
    }

    if (!Prev)
      head = head->next;
    else
      Prev->next = Lazy->next;
    delete Lazy;

    return head;
  }
};

这题是前后双指针,不是快慢双指针,很有意思。

此外注意一下删除节点的写法。删除操作总是需要维护一个 Prev 的,我们可以通过判断它是否为空,来决定是否要把头节点后移。

二刷

说实话这个题目跟 142. 环形链表 II 有异曲同工之妙的,都是「你先走,我殿后」的情况,可以补充记忆。至于这道题的图示的话,大概是

$$ [_{L\to}^{E\to}\ 7\ 6\ [_L\ 5\ 4\ 3\ [^{E\to}\ 2\ 1\ [^E $$

类似于这种感觉吧。

class Solution {
public:
  ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode Dummy(0, head);
    ListNode *Eager = head, *Lazy = head, *Prev = &Dummy;
    while (n--)
      Eager = Eager->next;
    while (Eager) {
      Prev = Prev->next;
      Eager = Eager->next;
      Lazy = Lazy->next;
    }
    Prev->next = Lazy->next;
    delete Lazy;
    return Dummy.next;
  }
};

我这个其实叫三指针,其实压根用不着,因为你会发现 Lazy 其实就是 Prev->next,所以完全可以按照官解进行简化,让 Lazy 变得 Lazier:

class Solution {
public:
  ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode Dummy(0, head);
    ListNode *Eager = head, *Lazier = &Dummy;
    while (n--)
      Eager = Eager->next;
    while (Eager) {
      Eager = Eager->next;
      Lazier = Lazier->next;
    }
    ListNode *Delete = Lazier->next;
    Lazier->next = Delete->next;
    delete Delete;
    return Dummy.next;
  }
};

以后见到这个题就想着「卷狗(Eager)和懒狗(Lazier)」就行了。

三刷

class Solution {
public:
  ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *Pred = head;
    ListNode PreFollow(0, head), *Follow = &PreFollow;
    for (int i = 0; i < n; ++i)
      Pred = Pred->next;
    while (Pred) {
      Pred = Pred->next;
      Follow = Follow->next;
    }
    ListNode *Delete = Follow->next;
    if (Delete == head)
      head = Follow->next->next;
    Follow->next = Follow->next->next;
    delete Delete;
    return head;
  }
};

我操,对啊,这个写法压根用不着,因为我们在用Follow进行遍历的过程当中已经天然地修改了PreFollow.next,所以只需要返回PreFollow.next就可以了,牛逼。