LeetCode 138.随机链表的复制

Algorithm

  • [🤖 问题](#🤖 问题)
    • [🛠️ 要求与约束](#🛠️ 要求与约束)
  • [🎯 常用解法概述(两种)](#🎯 常用解法概述(两种))
      • [✅ 方法 A:三步原地交织法(推荐 --- O(n) 时间,O(1) 额外空间)](#✅ 方法 A:三步原地交织法(推荐 — O(n) 时间,O(1) 额外空间))
      • [🧭 方法 B:哈希映射法(直观 --- O(n) 时间,O(n) 额外空间)](#🧭 方法 B:哈希映射法(直观 — O(n) 时间,O(n) 额外空间))
  • [🧾 伪代码(方法 A:交织法)](#🧾 伪代码(方法 A:交织法))
  • [🧾 C++ 实现(方法 A:交织法 --- 推荐)](#🧾 C++ 实现(方法 A:交织法 — 推荐))
  • [🧠 详细逐步解释(方法 A)](#🧠 详细逐步解释(方法 A))
  • [✅ 正确性说明](#✅ 正确性说明)
  • [⏱️ 复杂度分析](#⏱️ 复杂度分析)
  • [⚠️ 边界与注意事项](#⚠️ 边界与注意事项)
  • [📚 其他补充(方法 B:哈希表示例简要)](#📚 其他补充(方法 B:哈希表示例简要))

🤖 问题

给你一个长度为 n 的链表,每个节点包含一个额外的随机指针 random,该指针可以指向链表中的任何节点或 null。请返回该链表的 深拷贝 (复制出一份完全独立的链表:节点值相同、nextrandom 指向复制链表中的新节点,且不指向原链表的节点)。


🛠️ 要求与约束

  • 返回新链表的头节点(复制链表由 n 个全新节点组成)。
  • 0 <= n <= 1000Node.val[-10^4,10^4]
  • 时间/空间要求:尽量做到O(n) 时间、O(1) 额外空间(不计返回的新链表空间)。

🎯 常用解法概述(两种)


✅ 方法 A:三步原地交织法(推荐 --- O(n) 时间,O(1) 额外空间)

思路分三步(经典且高效):

  1. 在每个原节点后面插入对应的新节点 :例如原链 A -> B -> C 变成 A -> A' -> B -> B' -> C -> C'(A' 为 A 的拷贝)。
  2. 设置新节点的 random 指针 :对每个原节点 node,如果 node.random != null,则 node.next.random = node.random.next(因为 node.next 是 node 的拷贝,node.random.next 是 node.random 的拷贝)。
  3. 拆分链表 :把交织链表分成原链和新链,恢复原链 next 指针,并把新节点串成独立链表返回。

优点:不需要额外哈希表,空间为常数(除了输出本身)。


🧭 方法 B:哈希映射法(直观 --- O(n) 时间,O(n) 额外空间)

  • 使用 unordered_map<原节点指针, 新节点指针>
  • 第一次遍历创建每个原节点的拷贝并放进 map;第二次遍历设置每个拷贝的 nextrandom 指针(映射 lookup)。
  • 更直观但使用 O(n) 额外空间。

🧾 伪代码(方法 A:交织法)

复制代码
function copyRandomList(head):
    if head is null: return null

    # 1) 在每个原节点后插入拷贝节点
    p = head
    while p != null:
        newNode = Node(p.val)
        newNode.next = p.next
        p.next = newNode
        p = newNode.next

    # 2) 设置拷贝节点的 random 指针
    p = head
    while p != null:
        if p.random != null:
            p.next.random = p.random.next
        # else p.next.random stays null
        p = p.next.next   # 跳到下一个原节点

    # 3) 拆分成两个链表(原链恢复,拷贝链独立)
    p = head
    copyHead = head.next
    while p != null:
        copy = p.next
        p.next = copy.next            # 恢复原节点的 next
        if copy.next != null:
            copy.next = copy.next.next
        p = p.next

    return copyHead

🧾 C++ 实现(方法 A:交织法 --- 推荐)

cpp 复制代码
#include <iostream>
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) return nullptr;

        // 1) 在每个原节点后面插入其拷贝
        Node* p = head;
        while (p) {
            Node* copy = new Node(p->val);
            copy->next = p->next;
            p->next = copy;
            p = copy->next;
        }

        // 2) 复制 random 指针
        p = head;
        while (p) {
            if (p->random) {
                p->next->random = p->random->next;
            }
            p = p->next->next; // 跳到下一个原节点
        }

        // 3) 拆分链表:恢复原链并提取拷贝链
        p = head;
        Node* copyHead = head->next;
        while (p) {
            Node* copy = p->next;
            p->next = copy->next;               // 恢复原链的 next
            p = p->next;                        // 移动到下一个原节点
            if (p) copy->next = p->next;        // 设置复制节点的 next(可能为 null)
        }

        return copyHead;
    }
};

🧠 详细逐步解释(方法 A)

  • 第一步插入 :对 p(原节点)做 p.next = new Node(p.val),并把 new.next 指向原 p.next。这样每个原节点后面紧跟着其拷贝。
  • 第二步 random 复制 :因为每个原节点 p 的拷贝是 p.next,而 p.random 的拷贝是 p.random.next(若 p.random!=null)。所以直接 p.next.random = p.random.next 即可。
  • 第三步拆分 :当前链: p -> p' -> q -> q' -> ...。要恢复原链 p->q->...(即 p.next = p'.next),并构建拷贝链 p'->q'->...(即 p'.next = q'.next,实现为 if (p) copy->next = p->next)。遍历结束后得到独立的拷贝链。

✅ 正确性说明

  • 插入后的结构保证每个原节点后紧跟其拷贝,random 指针复制时只需常数时间定位对应拷贝;
  • 拆分时逐个恢复原节点的 next 并连接拷贝节点的 next,最终两链都正确且互不指向对方的内存;
  • 每个原节点、新节点只被常量次操作,故整体线性时间。

⏱️ 复杂度分析

  • 时间复杂度 :O(n),三次遍历(插入、复制 random、拆分)各 O(n)。

  • 空间复杂度(额外):O(1),只使用若干临时指针和常数额外变量;新节点内存计入输出,不算作额外空间。

    对比哈希法:时间 O(n),空间 O(n)。


⚠️ 边界与注意事项

  • 输入 head == nullptr 时返回 nullptr
  • 链表中 random 可能为 null,处理时要判空。
  • 错误写法容易在拆分时访问空指针,拆分循环写法要小心:p = p->next 必须跟随恢复后的原链结构。
  • 保证 new 创建的节点在拆分时被串成新的链并最终返回。

📚 其他补充(方法 B:哈希表示例简要)

若不要求 O(1) 额外空间,可用 unordered_map<Node*, Node*> mp

  1. 遍历原链,创建每个 orig -> copy 的映射;
  2. 再遍历一次,用 mp[orig]->next = mp[orig->next](若 orig->next 存在)和 mp[orig]->random = mp[orig->random](若存在)设置指针;
  3. 返回 mp[head]

相关推荐
zhengjianyang&1232 小时前
美团滑块-[behavior] 加密分析
javascript·经验分享·爬虫·算法·node.js
翟天保Steven2 小时前
ITK-基于欧拉变换与质心对齐的二维刚性配准算法
算法
那个什么黑龙江3 小时前
关于C++中的“类中的特殊成员函数”
开发语言·c++
Simucal3 小时前
基于物理引导粒子群算法的Si基GaN功率器件特性精准拟合
人工智能·算法·生成对抗网络
烦躁的大鼻嘎4 小时前
【Linux】深入探索多线程编程:从互斥锁到高性能线程池实战
linux·运维·服务器·开发语言·c++·算法·ubuntu
今后1234 小时前
【数据结构】快速排序与归并排序的实现
数据结构·算法·归并排序·快速排序
Wins_calculator5 小时前
C++编程的救赎:从反人性的编译到极速Vim工作流
c++·vim·wsl
Algo-hx5 小时前
数据结构入门 (三):链表的时空博弈 —— 循环链表与哑节点详解
数据结构·链表
南莺莺5 小时前
树的存储结构
数据结构·算法·