【算法】【链表】237.删除链表中的节点--通俗讲解

73 阅读5分钟

一、题目是啥?一句话说清

给定一个链表中的节点node,但无法访问头节点,要求删除这个节点。删除后,链表中的节点数减少一,且node前面的所有值顺序相同,后面的所有值顺序相同。

示例:

  • 输入:链表 4 → 5 → 1 → 9,需要删除节点5
  • 输出:链表变为 4 → 1 → 9

二、解题核心

将当前节点的值替换为下一个节点的值,然后将当前节点的next指针指向下一个节点的下一个节点,从而跳过下一个节点。这相当于删除了当前节点,因为它的值被覆盖了。

这就像在游戏中,你无法直接删除自己,但你可以变成下一个人,然后跳过下一个人,这样你就从游戏中消失了。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 无法访问前一个节点

  • 是什么:由于只给定要删除的节点node,而没有头节点,我们无法直接修改前一个节点的next指针。
  • 为什么重要:这限制了传统的删除方式,迫使我们需要一种创新的方法。

2. 复制下一个节点的值

  • 是什么:将node的值设置为node->next的值,这样node的值就不再是原来的值,而是下一个节点的值。
  • 为什么重要:这样就从值的角度“删除”了当前节点,因为它的值被覆盖了。

3. 跳过下一个节点

  • 是什么:将node的next指针指向node->next->next,从而绕过下一个节点。
  • 为什么重要:这从链表结构上删除了下一个节点,但由于我们已经复制了值,效果上相当于删除了当前节点。

四、看图理解流程(通俗理解版本)

假设链表为:4 → 5 → 1 → 9,需要删除节点5。

  1. 初始状态:节点5的值是5,next指向节点1。
  2. 复制值:将节点5的值改为下一个节点1的值,所以节点5的值变为1。现在链表看起来像:4 → 1 → 1 → 9(但节点5现在值为1)。
  3. 跳过下一个节点:将节点5的next指针指向节点1的next(即节点9)。所以节点5现在指向节点9。链表变为:4 → 1 → 9。
  4. 效果:节点5实际上被“删除”了,因为它的值变成了1,而原来的节点1被跳过了。注意,我们实际上删除了节点1,但因为我们复制了它的值,看起来像是删除了节点5。

五、C++ 代码实现(附详细注释)

#include <iostream>
using namespace std;

// 链表节点定义
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
public:
    void deleteNode(ListNode* node) {
        // 将当前节点的值替换为下一个节点的值
        node->val = node->next->val;
        // 保存下一个节点,以便后续删除
        ListNode* temp = node->next;
        // 将当前节点的next指针指向下一个节点的next
        node->next = node->next->next;
        // 删除下一个节点(释放内存)
        delete temp;
    }
};

// 辅助函数:打印链表(用于测试,但注意我们无法从头打印,因为没有head)
// 为了测试,我们需要构建链表
void printList(ListNode* head) {
    while (head != nullptr) {
        cout << head->val << " ";
        head = head->next;
    }
    cout << endl;
}

// 测试代码
int main() {
    // 构建示例链表:4->5->1->9
    ListNode* head = new ListNode(4);
    head->next = new ListNode(5);
    ListNode* toDelete = head->next; // 要删除的节点5
    head->next->next = new ListNode(1);
    head->next->next->next = new ListNode(9);
    
    cout << "Original list: ";
    printList(head);
    
    Solution solution;
    solution.deleteNode(toDelete);
    
    cout << "After deletion: ";
    printList(head);
    
    // 释放内存(简单示例)
    while (head != nullptr) {
        ListNode* temp = head;
        head = head->next;
        delete temp;
    }
    
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(1),因为只进行了常数次操作。
  • 空间复杂度:O(1),只使用了常数额外空间。

七、注意事项

  • 节点不是最后一个:题目保证给定的节点node不是链表中的最后一个节点,所以我们可以安全地访问node->next。
  • 内存管理:在C++中,我们需要手动删除被跳过的节点,以避免内存泄漏。但在其他语言如Java或Python中,垃圾回收会自动处理。
  • 值唯一性:链表值唯一,但这种方法不依赖于值唯一,只是题目条件。
  • 实际删除的是下一个节点:从技术上讲,我们删除的是下一个节点,但由于复制了值,效果上删除了当前节点。这可能会在某些场景下有问题(例如如果节点有其他属性),但根据题目要求,只关心值。

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解
【算法】【链表】160.相交链表--通俗讲解
【算法】【链表】203.移除链表元素--通俗讲解
【算法】【链表】206.反转链表--通俗讲解
【算法】234.回文链表--通俗讲解