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

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

题目描述

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

示例 1:
在这里插入图片描述

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

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

将删除倒数第n个节点转化为删除第n个节点

  先用一个哑节点(虚拟头节点)来简化删除头节点的情况。然后,它通过第一次遍历计算出链表的长度,接着计算出要到达的节点,最后通过第二次遍历找到目标节点的前一个节点。通过修改指针,实现了目标节点的删除,并且在结束前释放了删除节点和哑节点占用的内存。最终返回了新链表的头节点。
  删除第n个节点时需要找到前一个节点才能删除
在这里插入图片描述

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0); // 创建一个哑节点,其值为0,next指向链表的头节点head
        dummyhead->next = head;
        ListNode* cur = dummyhead; // 创建一个指针cur,初始化为指向哑节点
        int size = 0;
        while(cur) { // 遍历整个链表来计算链表的长度
            cur = cur->next;
            size++;
        }
        cur = dummyhead; // 重置cur指针回到哑节点的位置,准备第二次遍历
        int a = size - n - 1; // 计算正向第size-n个节点的索引(因为哑节点的存在,所以要额外减1)

        while(a--) { // 移动cur指针到正向第size-n个节点的前一个节点上
            cur = cur->next;
        }
        ListNode* tmp = cur->next; // 创建一个临时指针tmp,指向要删除的节点
        cur->next = cur->next->next; // 将要删除节点的前一个节点的next指向要删除节点的下一个节点,从而断开连接

        delete tmp; // 删除tmp指针指向的节点,释放内存

        ListNode* newHead = dummyhead->next; // 保存新的头节点,即哑节点的下一个节点
        delete dummyhead; // 删除哑节点,释放内存
        
        return newHead; // 返回新的头节点
    }
};

双指针

  通过使用两个指针来实现的,一个快指针(fast)首先向前移动 n 步,然后快指针和慢指针(slow)一起移动直到快指针到达链表尾部。此时慢指针指向的是倒数第 n 个节点的前一个节点。然后该节点被删除,并释放其内存。最后,函数删除了在开始时创建的哑节点(虚拟头节点),并返回链表的新头节点。

  • fast先移动n步
    在这里插入图片描述
  • 然后fast和slow再同时移动,fast移动到NULL,slow随着fast一起移动就能找到需要删除的节点,因为删除节点需要找到删除节点的前一个结点,所以fast只需要移动到NULL的前一个结点,slow结点也就移动到删除节点的前一个结点
    在这里插入图片描述
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // 创建一个哑节点(dummy head),其值不重要,目的是为了简化边缘情况的处理,
        // 比如删除的是链表的头节点。哑节点的下一个节点是链表的实际头节点。
        ListNode* dummyhead = new ListNode(0);
        dummyhead->next = head;

        // 创建两个指针,fast 和 slow,都开始时指向哑节点
        ListNode* fast = dummyhead;
        ListNode* slow = dummyhead;

        // 移动快指针fast,使其领先慢指针slow n 个节点。
        // 这是为了当fast指针到达链表末尾时,slow指针将指向倒数第n个节点的前一个节点。
        while(n--)
            fast = fast->next;

        // 同时移动快指针和慢指针,直到快指针到达最后一个节点。
        // 这样做是为了找到倒数第n个节点,因为当快指针到达链表尾部时,
        // 慢指针将会指向倒数第n个节点前的一个节点。
        while(fast->next != nullptr) {
            fast = fast->next;
            slow = slow->next;
        }

        // slow指针现在指向倒数第n个节点的前一个节点,
        // tmp是倒数第n个节点,准备删除这个节点。
        ListNode* tmp = slow->next;

        // 断开倒数第n个节点的连接,将其前一个节点的next指向其下一个节点,
        // 从而将其从链表中移除。
        slow->next = slow->next->next;

        // 删除tmp指针指向的节点,即释放倒数第n个节点的内存。
        delete tmp;
        // 将tmp设置为nullptr,防止悬挂指针导致的未定义行为。
        tmp = nullptr;

        // newhead是指向链表新头节点的指针,即哑节点之后的节点。
        ListNode* newhead = dummyhead->next;

        // 删除哑节点,释放其内存。
        delete dummyhead;
        // 将dummyhead设置为nullptr,防止悬挂指针导致的未定义行为。
        dummyhead = nullptr;

        // 返回链表的新头节点。
        return newhead;
    }
};