【链表】LC 138.随机链表的复制

文章目录

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

138.随机链表的复制

2、题目描述

二、个人思路整理

1、思路分析

思路1:哈希表映射法

具体步骤:

  1. 建立映射:使用哈希表unordered_map<Node*, Node*> 维护 原节点 -> 新节点 的一一对应关系。

  2. 分步构建:

    • 第一轮遍历:仅创建新节点并保存数值(val),同时存入哈希表中,不处理指针指向。
    • 第二轮遍历:借助哈希表,通过原节点的指针关系直接设置新节点的指向:
      • 新节点的 next 指向原节点 next 对应的新节点:mp[cur]->next = mp[cur->next];
      • 新节点的 random 指向原节点 random 对应的新节点:mp[cur]->random = mp[cur->random]。

思路2:节点交织与拆分法

无需额外哈希表,利用原链表自身的 next 指针构造"天然的映射关系":

  1. 复制与拼接:在每个原节点 A A A 后面紧跟插入其对应的复制节点 A ′ A' A′。
    • 变换前:A -> B -> C;
    • 变换后:A -> A' -> B -> B' -> C -> C'。
  2. 构建 random 指针:若原节点 A A A 的 random 指向 C C C(即 cur->random),则复制节点 A ′ A' A′(即 cur->next)的 random 必然指向 C C C 的复制节点 C ′ C' C′(即 cur->random->next)。
  3. 拆分链表:将交织在一起的链表分离,恢复原链表结构,同时拼接出全新的深拷贝链表。

2、解题代码

思路1:哈希表映射法

cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (!head) {
            return nullptr;
        }

        // 用于存储 原节点->新节点 的映射关系
        unordered_map<Node*, Node*> mp;

        // 第一遍遍历:只创建新节点,并建立映射
        Node* cur = head;
        while (cur) {
            mp[cur] = new Node(cur->val);
            cur = cur->next;
        }

        // 第二遍遍历:通过哈希表设置新节点的next和random指针
        cur = head;
        while (cur) {
            // 如果原节点的 next/random 为nullptr,mp[nullptr]默认返回 nullptr
            mp[cur]->next = mp[cur->next];
            mp[cur]->random = mp[cur->random];
            cur = cur->next;
        }
        // 返回头节点对应的新节点
        return mp[head];
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),遍历了两遍链表。
  • 空间复杂度: O ( n ) O(n) O(n),额外维护了一个哈希表 mp。

思路2:节点交织与拆分法

cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (!head) {
            return nullptr;
        }

        // 1. 复制每一个节点,并插入到对应原节点之后
        Node* cur = head;
        while (cur) {
            Node* copyNode = new Node(cur->val);
            copyNode->next = cur->next;    // 新节点指向原节点的下一个节点
            cur->next = copyNode;     // 原节点指向新节点
            cur = copyNode->next;     // 移动到下一个原节点
        }

        // 2. 为复制出来的节点构造 random 指针
        cur = head;
        while (cur) {
            if (cur->random) {
                // cur->next 是新节点,cur->random->next 是random目标节点对应的新节点
                cur->next->random = cur->random->next;
            }
            cur = cur->next->next;  // 跨过复制节点,移动到下一个原节点
        }

        // 3. 拆分链表,恢复原链表并提取出复制链表
        cur = head;
        Node* dummy = new Node(0);  // 虚拟头节点,方便串联新链表
        Node* copyCur = dummy;

        while (cur) {
            Node* copyNode = cur->next;
            cur->next = copyNode->next;   // 恢复原链表的next指针

            copyCur->next = copyNode;     // 将新节点接入新链表
            copyCur = copyCur->next;

            cur = cur->next;       // 移动到下一个原节点
        }

        return dummy->next;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),三次线性遍历链表。
  • 空间复杂度: O ( 1 ) O(1) O(1),若干临时指针变量空间。

三、知识风暴

哈希表映射 + 指针重建 是本题的核心思想:利用哈希表建立 原节点 -> 新节点 的一一映射,先创建所有新节点,再借助映射关系重建 next 与 random 指针,从而完成链表的深拷贝。

算法核心思想:

  • 哈希表映射 :unordered_map<Node*, Node*> 维护原节点与新节点的对应关系,实现 O ( 1 ) O(1) O(1) 的指针映射查找。
  • 两轮遍历 :第一轮只创建新节点并建立映射,第二轮借助映射重建 next 与 random 指针。
  • 空指针处理 :mp[nullptr] 默认返回 nullptr,天然处理了 next/random 为空的边界情况。
  • 一次深拷贝 :整个过程只需遍历链表两遍,时间复杂度为 O ( n ) O(n) O(n)。

常见对比:哈希表法 vs 节点交织法

方法 核心思路 时间复杂度 空间复杂度 适用场景
哈希表法 哈希表建立映射,两轮遍历重建指针 O ( n ) O(n) O(n) O ( n ) O(n) O(n) 思路直观、易于理解,面试最常考察,无指针操作风险
节点交织法 复制节点插入原节点之后,再拆分链表 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) 空间更优,但指针操作复杂,需注意拆分时恢复原链表

使用要点:

  • 映射建立 :第一轮遍历时 mp[cur] = new Node(cur->val),只复制数值不处理指针。
  • 指针重建 :第二轮遍历时 mp[cur]->next = mp[cur->next]、mp[cur]->random = mp[cur->random],借助映射一次性完成。
  • 空指针安全 :mp[nullptr] 默认返回 nullptr,无需额外判断 cur->next 或 cur->random 是否为空。
  • 返回头节点 :最终返回 mp[head],即原链表头节点对应的新节点。
  • 递归终止条件 :head == nullptr 时直接返回 nullptr,空链表无需拷贝。

算法变体与扩展:

  1. 深拷贝带随机指针的二叉树:将哈希表映射思想推广到二叉树,先复制节点再重建左右子树与随机指针(对应 LeetCode 1485)。
  2. 克隆图:借助哈希表记录已访问节点,DFS/BFS 遍历图并复制节点与邻接关系(对应 LeetCode 133)。
  3. 复制带随机指针的链表(递归版):用递归 + 哈希表缓存已复制节点,避免重复复制(对应 LeetCode 138 的递归解法)。
  4. 序列化与反序列化二叉树:借助哈希表或索引重建节点间的父子关系,与本题的映射思想异曲同工(对应 LeetCode 297)。

与其他算法的对比:

  • 哈希表法 : O ( n ) O(n) O(n) 时间、 O ( n ) O(n) O(n) 空间,思路清晰、实现简单,是本题最直观的解法。
  • 节点交织法 : O ( n ) O(n) O(n) 时间、 O ( 1 ) O(1) O(1) 空间,空间更优,但需注意拆分时恢复原链表结构,指针操作易出错。

相关 LeetCode 例题:

相关推荐
m0_547486663 天前
《数据结构教程》全套 PPT课件2026
数据结构
tryxr3 天前
矩阵的几种基础变换
java·数据结构·算法·矩阵
mmmmath_33 天前
LeetCode.028.找出字符串中第一个匹配项的
数据结构·算法·leetcode
All for pursuit.3 天前
【栈-4】739.每日温度
数据结构·c++·算法·leetcode
All for pursuit.3 天前
【栈-5】84.柱状图中最大的矩形
数据结构·c++·算法·leetcode
渡我白衣3 天前
HttpRequest与HttpResponse的实现
服务器·数据结构·c++·人工智能·tcp/ip·机器学习·caffe
晴天的雨.9924 天前
【C++算法】和为s的两个数
开发语言·数据结构·c++·算法
无敌贵点大王4 天前
RTThread学习记录11——RT-Thread 设备模型吃透:UART/ADC/PWM/PIN 到底有什么区别?
c语言·stm32·学习·链表
淡海水4 天前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
Logic1014 天前
C语言/数据结构位运算题解:异或XOR找出时尚聚会中的“独特颜色“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质