【链表】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),若干临时指针变量空间。

三、知识风暴

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

算法核心思想

  • 哈希表映射unordered_map<Node*, Node*> 维护原节点与新节点的对应关系,实现 O ( 1 ) O(1) O(1) 的指针映射查找。
  • 两轮遍历 :第一轮只创建新节点并建立映射,第二轮借助映射重建 nextrandom 指针。
  • 空指针处理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->nextcur->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 例题

相关推荐
洋不写bug2 小时前
链表补充练习,双链表的模拟实现
java·数据结构·链表·双链表·底层实现
白狐_7982 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法
heima20163 小时前
长期复盘:拼团活动链接开发公司的行业现状与困境洞察
链表
mmmmath_34 小时前
面试题 02.07. 链表相交
算法·链表
sylviiiiiia4 小时前
Leetcode hot100 多数元素/相交链表/反转链表
算法·leetcode·链表
我不会起名字3225 小时前
一天一道算法题(26):栈的简单应用
java·数据结构·python·算法·leetcode·golang·
不会就选b5 小时前
算法日常・每日刷题--<贪心+大根堆>2
数据结构·算法
事圆则缓5 小时前
Java 常见数据结构与 Android 使用场景
android·java·数据结构
白狐_7986 小时前
408 数据结构|外部排序优化:怎么减少时间开销
数据结构·算法