【算法--链表】138.随机链表的复制--通俗讲解

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解


通俗易懂讲解"随机链表的复制"算法题目

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

给你一个链表,每个节点有一个随机指针,指向链表中的任意节点或空。你需要深拷贝这个链表,创建全新节点,并正确设置next和random指针。

示例:

  • 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
  • 输出:复制后的链表,其中每个新节点的random指向新链表中的对应节点。

二、解题核心

使用哈希表来映射原节点到新节点。首先遍历原链表,创建所有新节点并存储映射关系。然后再次遍历原链表,根据映射关系设置新节点的next和random指针。

这就像先复制所有人的身份证(创建新节点),然后根据原关系网(原链表)来建立新关系网(新链表)。

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

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

1. 哈希表的映射作用

  • 是什么:使用哈希表存储原节点和新节点的对应关系,这样可以通过原节点快速找到新节点。
  • 为什么重要:当设置random指针时,我们需要知道原节点对应的新节点是什么。哈希表提供了O(1)的查找效率,确保正确连接random指针。

2. 两次遍历链表

  • 是什么:第一次遍历创建新节点并建立映射;第二次遍历设置指针。
  • 为什么重要:第一次遍历确保所有新节点都被创建;第二次遍历利用映射正确设置random指针,因为random可能指向任何节点,需要通过哈希表找到对应的新节点。

3. 处理null指针的情况

  • 是什么:如果原节点的random指针为null,新节点的random也应为null。
  • 为什么重要:避免空指针异常,并确保复制准确。在访问哈希表时,需要检查原指针是否为null,否则会出错。

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

假设原链表为:A -> B -> C,其中A.random指向C,B.random指向A,C.random为null。

  1. 第一次遍历:创建新节点和映射

    • 遍历原链表,对于每个节点,创建新节点,值相同。
    • 将原节点A映射到新节点A',原节点B映射到新节点B',原节点C映射到新节点C'。
    • 此时新节点还没有连接next和random。
  2. 第二次遍历:设置指针

    • 再次遍历原链表,对于每个原节点,通过哈希表找到对应的新节点。
    • 设置新节点的next:原节点A的next是B,所以新节点A'的next应该是B'(通过哈希表得到)。
    • 设置新节点的random:原节点A的random是C,所以新节点A'的random应该是C'(通过哈希表得到)。同理,B的random是A,所以B'的random是A'。C的random为null,所以C'的random为null。
    • 这样新链表就完整了。

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

cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

// 链表节点定义
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = nullptr;
        random = nullptr;
    }
};

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr) return nullptr;
        
        unordered_map<Node*, Node*> map; // 哈希表:原节点 -> 新节点
        
        // 第一次遍历:创建新节点并建立映射
        Node* curr = head;
        while (curr != nullptr) {
            map[curr] = new Node(curr->val);
            curr = curr->next;
        }
        
        // 第二次遍历:设置next和random指针
        curr = head;
        while (curr != nullptr) {
            // 设置next指针:如果curr->next不为null,则映射到新节点;否则为null
            map[curr]->next = curr->next ? map[curr->next] : nullptr;
            // 设置random指针:如果curr->random不为null,则映射到新节点;否则为null
            map[curr]->random = curr->random ? map[curr->random] : nullptr;
            curr = curr->next;
        }
        
        return map[head];
    }
};

// 测试代码(简单示例)
int main() {
    // 构建示例链表:节点0:值7, random null; 节点1:值13, random指向0; 等等。
    Node* head = new Node(7);
    head->next = new Node(13);
    head->next->next = new Node(11);
    head->next->next->next = new Node(10);
    head->next->next->next->next = new Node(1);
    
    head->random = nullptr;
    head->next->random = head; // 指向节点0
    head->next->next->random = head->next->next->next->next; // 指向节点4
    head->next->next->next->random = head->next->next; // 指向节点2
    head->next->next->next->next->random = head; // 指向节点0
    
    Solution solution;
    Node* copyHead = solution.copyRandomList(head);
    
    // 打印复制后的链表(简单打印值)
    Node* curr = copyHead;
    while (curr != nullptr) {
        cout << "val: " << curr->val;
        if (curr->random != nullptr) {
            cout << ", random: " << curr->random->val;
        } else {
            cout << ", random: null";
        }
        cout << endl;
        curr = curr->next;
    }
    
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(n),其中n是链表长度。我们遍历链表两次,每次都是O(n)。
  • 空间复杂度:O(n),因为哈希表存储了n个键值对。

七、注意事项

  • 处理null指针:在设置next和random指针时,必须检查原指针是否为null,否则访问map[null]会导致未定义行为。
  • 哈希表覆盖:确保每个原节点只被映射一次,避免重复创建新节点。
  • 深拷贝的含义:新链表节点必须是全新的,不能指向原链表的节点。哈希表方法自然保证了这一点。
  • 内存管理:在实际应用中,需要注意内存释放,但本题只要求复制,不涉及释放原链表。

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解

相关推荐
学习永无止境@3 分钟前
MATLAB中矩阵转置
算法·matlab·fpga开发·矩阵
Wect3 分钟前
JS手撕:函数进阶 & 设计模式解析
前端·javascript·面试
七颗糖很甜3 分钟前
雨滴谱数据深度解析——从原始变量到科学产品的Python实现【下篇】
python·算法·pandas
nlpming3 分钟前
OpenClaw system prompt定义
算法
nlpming4 分钟前
OpenClaw安装配置及简介
算法
爱码小白4 分钟前
MySQL 常用数据类型的系统总结
数据库·python·算法
玛丽莲茼蒿11 分钟前
Leetcode hot100 【中等】括号生成
算法·leetcode·职场和发展
小欣加油14 分钟前
leetcode 128 最长连续序列
c++·算法·leetcode·职场和发展
前端摸鱼匠25 分钟前
【AI大模型春招面试题18】 L1、L2正则化、Dropout、早停(Early Stopping)的原理与适用场景?
人工智能·ai·语言模型·面试·大模型
汀、人工智能26 分钟前
[特殊字符] 第94课:删除无效的括号
数据结构·算法·数据库架构·图论·bfs·删除无效的括号