【算法--链表】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.填充每个节点的下一个右侧节点指针--通俗讲解

相关推荐
anlogic2 小时前
Java基础 9.10
java·开发语言·算法
薛定谔的算法2 小时前
JavaScript单链表实现详解:从基础到实践
数据结构·算法·leetcode
yongche_shi2 小时前
第二篇:Python“装包”与“拆包”的艺术:可迭代对象、迭代器、生成器
开发语言·python·面试·面试宝典·生成器·拆包·装包
CoovallyAIHub2 小时前
CostFilter-AD:用“匹配代价过滤”刷新工业质检异常检测新高度! (附论文和源码)
深度学习·算法·计算机视觉
幻奏岚音2 小时前
《数据库系统概论》第一章 初识数据库
数据库·算法·oracle
你好,我叫C小白2 小时前
贪心算法(最优装载问题)
算法·贪心算法·最优装载问题
CoovallyAIHub3 小时前
CVPR 2025 | 频率动态卷积(FDConv):以固定参数预算实现频率域自适应,显著提升视觉任务性能
深度学习·算法·计算机视觉
mit6.8243 小时前
[rStar] 解决方案节点 | `BaseNode` | `MCTSNode`
人工智能·python·算法
独行soc3 小时前
2025年渗透测试面试题总结-60(题目+回答)
java·python·安全·web安全·adb·面试·渗透测试