【链表】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 例题:

相关推荐
开发小能手1236 小时前
答案daanC语言
数据结构·算法
启观川10 小时前
数据结构与算法-第 3 章 常用算法-贪心算法
数据结构·笔记·python·算法·贪心算法
程序员清风11 小时前
堆与优先队列:从 Top K 到任务调度
java·数据结构·算法
suhan11 小时前
【数据结构】四、串:串string、模式匹配BF\KMP、广义表
数据结构·后端·算法
啵啵啵鱼11 小时前
第二章 线性表
java·数据结构·算法
局部有解11 小时前
Hot100 - 三数之和
数据结构·c++·算法
辉艺棉麻小张11 小时前
用 JSON 建模面料规格:成分、克重、支数与公定回潮率字段设计
数据结构
longlongzihan12 小时前
C++ 常用数据结构速查:vector、stack、queue、list、树、图、哈希表等声明与核心操作
数据结构·c++·list
爱读书的小胖16 小时前
LeetCode 27. 移除元素(C)解题分析
数据结构·算法
爱读书的小胖16 小时前
Codewars: A Brave New Wordle(C)解题分析
c语言·数据结构·算法