【每日算法】LeetCode138. 随机链表的复制

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

138. 随机链表的复制:从链表到图的深拷贝思维

1. 题目描述

给定一个长度为 n 的链表,每个节点除了包含一个 val 字段和一个指向下一个节点的 next 指针外,还包含一个可指向链表中任意节点或 nullrandom 指针。

请完成这个链表的 深拷贝 。深拷贝应该完全复制原链表的所有节点及其关系(包括 nextrandom 指针),返回复制链表的头节点。

示例:

javascript 复制代码
// 原始链表结构
const node1 = { val: 7, next: node2, random: null };
const node2 = { val: 13, next: node3, random: node1 };
const node3 = { val: 11, next: node4, random: node5 };
const node4 = { val: 10, next: node5, random: node3 };
const node5 = { val: 1, next: null, random: node1 };

2. 问题分析

这是一个典型的 数据结构深拷贝 问题,特殊之处在于每个节点有两个指针,其中一个 (random) 可以指向链表中的任意节点,形成了潜在的 图结构(而非简单的线性结构)。

核心挑战:

  • random 指针可能指向尚未创建的节点,也可能形成循环引用
  • 需要保持新链表节点间的对应关系,避免重复创建同一原始节点的多个副本
  • 本质上是对一个有向图的遍历和复制问题

前端视角:

这类似于在前端中深拷贝一个包含循环引用的复杂对象。例如,一个组件树中某个组件引用了另一个组件,两个组件又互相引用的情况。

3. 解题思路

3.1 哈希表映射法(最优解)

使用哈希表建立原节点到新节点的映射关系:

  1. 第一次遍历:创建所有新节点并存入哈希表,建立原节点→新节点的映射
  2. 第二次遍历:根据哈希表设置新节点的 nextrandom 指针

时间复杂度: O(n)
空间复杂度: O(n)

3.2 原地修改拆分法

不额外使用哈希表,通过修改原链表结构实现:

  1. 在每个原节点后插入对应的复制节点
  2. 设置复制节点的 random 指针
  3. 拆分两个链表,恢复原链表结构

时间复杂度: O(n)
空间复杂度: O(1)(不考虑输出占用的空间)

4. 各思路代码实现

4.1 哈希表映射法

javascript 复制代码
/**
 * @param {Node} head
 * @return {Node}
 */
const copyRandomList = function(head) {
    if (!head) return null;
    
    const map = new Map();
    let curr = head;
    
    // 第一遍遍历:创建所有新节点并建立映射
    while (curr) {
        map.set(curr, new Node(curr.val));
        curr = curr.next;
    }
    
    // 第二遍遍历:连接指针
    curr = head;
    while (curr) {
        const newNode = map.get(curr);
        if (curr.next) newNode.next = map.get(curr.next);
        if (curr.random) newNode.random = map.get(curr.random);
        curr = curr.next;
    }
    
    return map.get(head);
};

4.2 原地修改拆分法

javascript 复制代码
/**
 * @param {Node} head
 * @return {Node}
 */
const copyRandomList = function(head) {
    if (!head) return null;
    
    // 1. 在每个节点后插入复制节点
    let curr = head;
    while (curr) {
        const copy = new Node(curr.val);
        copy.next = curr.next;
        curr.next = copy;
        curr = copy.next;
    }
    
    // 2. 设置复制节点的random指针
    curr = head;
    while (curr) {
        if (curr.random) {
            curr.next.random = curr.random.next;
        }
        curr = curr.next.next;
    }
    
    // 3. 拆分两个链表
    const dummy = new Node(0);
    let copyCurr = dummy;
    curr = head;
    
    while (curr) {
        copyCurr.next = curr.next;
        copyCurr = copyCurr.next;
        
        // 恢复原链表
        curr.next = curr.next.next;
        curr = curr.next;
    }
    
    return dummy.next;
};

5. 各实现思路的复杂度、优缺点对比

方法 时间复杂度 空间复杂度 优点 缺点 适用场景
哈希表映射法 O(n) O(n) 逻辑清晰,易于理解;不修改原链表 需要额外O(n)空间存储映射关系 大多数场景,尤其是不能修改原链表的场景
原地修改拆分法 O(n) O(1) 空间效率高;不需要额外数据结构 修改原链表结构;代码逻辑相对复杂 内存受限且允许修改原链表的场景

6. 总结

6.1 算法核心要点

  1. 图的深拷贝思维:随机链表本质上是一个有向图,需要避免循环引用和重复创建
  2. 映射关系的维护:无论是哈希表还是原地插入,核心都是建立原节点到新节点的对应关系
  3. 两次遍历模式:创建节点→连接指针,这是解决此类问题的通用模式

6.2 前端应用场景

6.2.1 复杂对象深拷贝

在处理包含循环引用的复杂对象时,可以借鉴这种思路:

javascript 复制代码
// 类似思路的深拷贝函数
function deepClone(obj, map = new Map()) {
    if (!obj || typeof obj !== 'object') return obj;
    if (map.has(obj)) return map.get(obj);
    
    const clone = Array.isArray(obj) ? [] : {};
    map.set(obj, clone);
    
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key], map);
        }
    }
    return clone;
}
6.2.2 组件/节点复制
  • UI组件树的复制:当组件间存在相互引用时,需要类似的映射机制
  • DOM节点复制与操作:复制复杂的DOM结构并保持事件监听器等引用关系
  • 状态管理:在Redux或Vuex中复制包含循环引用的状态树
6.2.3 数据流处理
  • 复杂数据结构的序列化/反序列化:如处理嵌套评论、回复关系
  • 图数据库查询结果的复制:保持节点间的关系不变

6.3 思维提升价值

  1. 从线性到非线性思维:链表→图,这是数据结构认知的重要升级
  2. 空间换时间的权衡:哈希表法是典型的空间换时间策略
  3. 原地算法设计:在不使用额外空间的情况下解决问题,这对性能敏感的前端应用(如图形编辑器、游戏)尤为重要

通过这道题,我们不仅学会了一个具体算法,更重要的是掌握了处理复杂引用关系、循环引用的通用思维模型。这种能力在前端优化、复杂状态管理、性能调优等方面都有广泛应用,是从初级前端迈向资深开发的重要标志。

相关推荐
zore_c3 小时前
【C语言手撕算法】LeetCode-142. 环形链表 II(C语言)
c语言·数据结构·算法·leetcode·链表·推荐算法
hnjzsyjyj9 小时前
东方博宜OJ 2190:树的重心 ← 邻接表 or 链式前向星
数据结构·链式前向星·树的重心
yaoh.wang11 小时前
力扣(LeetCode) 13: 罗马数字转整数 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
ChoSeitaku11 小时前
NO15数据结构选择题考点|线性表|栈和队列|串
数据结构
T1ssy11 小时前
布隆过滤器:用概率换空间的奇妙数据结构
算法·哈希算法
hetao173383712 小时前
2025-12-12~14 hetao1733837的刷题笔记
数据结构·c++·笔记·算法
一直都在57212 小时前
数据结构入门:时间复杂度与排序和查找
数据结构
鲨莎分不晴12 小时前
强化学习第五课 —— A2C & A3C:并行化是如何杀死经验回放
网络·算法·机器学习
搞科研的小刘选手13 小时前
【ISSN/ISBN双刊号】第三届电力电子与人工智能国际学术会议(PEAI 2026)
图像处理·人工智能·算法·电力电子·学术会议