挑战刷leetcode第十天( 链表-删除链表的倒数第n个节点)

126 阅读4分钟

如何高效删除链表的倒数第N个节点?Java与C++双实现解析

在日常的算法练习或面试中,链表操作是一个常见且重要的主题。今天,我们将探讨一个经典问题:如何删除链表的倒数第N个节点?  这个问题看似简单,但其中蕴含的指针操作和边界条件处理却值得我们深入思考。本文将用Java和C++两种语言分别实现,并探讨其中的技术细节与优化思路。

问题背景

给定一个单链表,删除链表的倒数第N个节点,并返回链表的头节点。例如:

输入: 1 -> 2 -> 3 -> 4 -> 5, n = 2
输出: 1 -> 2 -> 3 -> 5

在这个例子中,我们需要删除倒数第2个节点,也就是值为4的节点。

解题思路

要解决这个问题,最直观的方法是先遍历链表,计算链表的长度,然后再遍历一次找到倒数第N个节点并删除。这种方法的时间复杂度是O(2L),其中L是链表的长度。

然而,我们可以通过双指针技巧将时间复杂度优化到O(L)。具体思路如下:

  1. 使用两个指针preend,初始时都指向链表的虚拟头节点(dummy node)。
  2. 移动pre指针:先让pre指针向前移动N步。
  3. 同时移动preend指针:然后同时移动preend指针,直到pre指针到达链表末尾。
  4. 删除节点:此时end指针指向的节点就是倒数第N个节点的前一个节点,直接删除倒数第N个节点即可。

这种方法的核心思想是通过双指针的间距来定位倒数第N个节点,避免了多次遍历链表。

Java实现

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode node = new ListNode(0); // 创建虚拟头节点
    node.next = head;

    ListNode pre = node;
    ListNode end = node;

    // 先移动pre指针,使其与end指针相距N个节点
    while (n != 0) {
        pre = pre.next;
        n--;
    }

    // 同时移动pre和end指针,直到pre到达链表末尾
    while (pre.next != null) {
        pre = pre.next;
        end = end.next;
    }

    // 删除倒数第N个节点
    end.next = end.next.next;

    return node.next; // 返回新的头节点
}

C++实现

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* node = new ListNode(0); // 创建虚拟头节点
    node->next = head;

    ListNode* pre = node;
    ListNode* end = node;

    // 先移动pre指针,使其与end指针相距N个节点
    while (n != 0) {
        pre = pre->next;
        n--;
    }

    // 同时移动pre和end指针,直到pre到达链表末尾
    while (pre->next != nullptr) {
        pre = pre->next;
        end = end->next;
    }

    // 删除倒数第N个节点
    end->next = end->next->next;

    return node->next; // 返回新的头节点
}

代码解析

  1. 虚拟头节点:我们创建了一个虚拟头节点node,并将其next指向链表的头节点。这样做的好处是可以统一处理删除头节点的情况,避免额外的边界判断。
  2. 双指针移动pre指针先移动N步,然后preend指针同时移动,直到pre指针到达链表末尾。此时,end指针指向倒数第N个节点的前一个节点。
  3. 删除节点:通过end->next = end->next->next,我们可以直接删除倒数第N个节点。

思考与优化

  1. 边界条件:在实际编码中,我们需要特别注意边界条件,例如链表为空或N大于链表长度的情况。虽然上述代码通过虚拟头节点简化了这些情况的处理,但在实际应用中,我们仍需确保代码的鲁棒性。
  2. 时间复杂度:双指针技巧将时间复杂度优化到了O(L),其中L是链表的长度。这是一种非常高效的解决方案,尤其适用于链表长度较大的场景。
  3. 空间复杂度:我们只使用了常数级别的额外空间,因此空间复杂度为O(1)。

坚持的意义

在算法学习的过程中,我们常常会遇到各种挑战和困难。然而,正是这些挑战让我们不断进步。通过坚持练习和深入思考,我们不仅能够掌握更多的算法技巧,还能培养出解决问题的灵活思维。每一次的代码优化和边界条件处理,都是我们技术成长的见证。

结语

删除链表的倒数第N个节点是一个经典的链表操作问题,通过双指针技巧,我们可以高效地解决这个问题。本文通过Java和C++两种语言的实现,详细解析了其中的技术细节,并探讨了优化思路和边界条件处理。希望这篇文章能够帮助你更好地理解链表操作,并在未来的算法学习中取得更大的进步。

坚持练习,持续进步,你一定能成为更好的自己!