138. 随机链表的复制 - 题解与详细分析

138. 随机链表的复制 - 题解与详细分析

题目描述

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random,该指针可以指向链表中的任何节点或空节点。

构造这个链表的深拷贝。深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点。

示例

示例 1:

text

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

text

输入:head = [[1,1],[2,1]] 输出:[[1,1],[2,1]]

示例 3:

text

输入:head = [[3,null],[3,0],[3,null]] 输出:[[3,null],[3,0],[3,null]]

解题思路

这道题的关键在于如何处理随机指针。由于随机指针可能指向链表中的任意节点,简单的遍历复制无法处理随机指针的指向问题。

核心思想:三步法

  1. 插入复制节点:在原链表的每个节点后面插入一个复制节点
  2. 设置随机指针:根据原节点的随机指针设置复制节点的随机指针
  3. 分离链表:将原链表和复制链表分离

这种方法的时间复杂度为 O(n),空间复杂度为 O(1)(不包括结果链表)。

代码实现

c

/** * Definition for a Node. * struct Node { * int val; * struct Node *next; * struct Node *random; * }; */ // 创建新节点函数 struct Node* BuyNode(int x) { struct Node* NewNode = (struct Node*)malloc(sizeof(struct Node)); NewNode->val = x; NewNode->next = NULL; NewNode->random = NULL; return NewNode; } struct Node* copyRandomList(struct Node* head) { if (head == NULL) return NULL; struct Node* cur = head; // 第一步:在原链表的每个节点后面拷贝一个节点 while(cur) { struct Node* next = cur->next; struct Node* newnode = BuyNode(cur->val); newnode->next = next; cur->next = newnode; cur = next; } // 第二步:设置random指针 cur = head; while(cur) { if(cur->random == NULL) { cur->next->random = NULL; } else { cur->next->random = cur->random->next; } cur = cur->next->next; } // 第三步:将新链表和旧链表断开,并各自链接 cur = head; struct Node* newhead = NULL; struct Node* newtail = NULL; while(cur) { // 将copy节点标记下来 struct Node* temp = cur->next; // 去掉copy节点,然后恢复原链表 cur->next = temp->next; if(newhead == NULL) { newhead = temp; newtail = temp; } else { newtail->next = temp; newtail = newtail->next; } cur = temp->next; } return newhead; }

代码详解

第一步:插入复制节点

c

while(cur) { struct Node* next = cur->next; struct Node* newnode = BuyNode(cur->val); newnode->next = next; cur->next = newnode; cur = next; }

执行效果:

text

原链表:A → B → C → NULL 插入后:A → A' → B → B' → C → C' → NULL

关键点:

  • 在每个原节点后面插入一个复制节点
  • 复制节点的值与原节点相同
  • 保持链表的连接关系

第二步:设置随机指针

c

while(cur) { if(cur->random == NULL) { cur->next->random = NULL; } else { cur->next->random = cur->random->next; } cur = cur->next->next; }

关键点:

  • 如果原节点的random为NULL,复制节点的random也为NULL
  • 如果原节点的random不为NULL,复制节点的random指向原节点random指向的节点的下一个节点(即对应的复制节点)
  • 因为每个原节点后面都跟着它的复制节点,所以 cur->random->next 就是原节点random指向的节点的复制节点

第三步:分离链表

c

while(cur) { struct Node* temp = cur->next; // 复制节点 cur->next = temp->next; // 恢复原链表 if(newhead == NULL) { newhead = temp; newtail = temp; } else { newtail->next = temp; newtail = newtail->next; } cur = temp->next; // 移动到下一个原节点 }

关键点:

  • 将复制节点从原链表中分离出来
  • 恢复原链表的next指针
  • 构建新的复制链表

执行过程可视化

以示例1为例:

原链表:

text

节点0: 7 -> random: NULL 节点1: 13 -> random: 节点0 节点2: 11 -> random: 节点4 节点3: 10 -> random: 节点2 节点4: 1 -> random: 节点0

第一步后:

text

7 → 7' → 13 → 13' → 11 → 11' → 10 → 10' → 1 → 1' → NULL

第二步后(设置random):

  • 7'.random = NULL
  • 13'.random = 7'
  • 11'.random = 1'
  • 10'.random = 11'
  • 1'.random = 7'

第三步后(分离):

  • 原链表恢复:7 → 13 → 11 → 10 → 1 → NULL
  • 复制链表:7' → 13' → 11' → 10' → 1' → NULL

复杂度分析

  • 时间复杂度:O(n),需要遍历链表三次
  • 空间复杂度:O(1),不包括结果链表,只使用常数级别的额外空间

其他解法

方法二:哈希表法

c

struct Node* copyRandomList(struct Node* head) { if (head == NULL) return NULL; // 创建哈希表,映射原节点到复制节点 struct Node* hash[1000] = {0}; int index = 0; // 第一次遍历:创建所有节点并建立映射 struct Node* cur = head; while (cur != NULL) { hash[index] = BuyNode(cur->val); cur = cur->next; index++; } // 第二次遍历:设置next和random指针 cur = head; index = 0; while (cur != NULL) { if (cur->next != NULL) { hash[index]->next = hash[index + 1]; } if (cur->random != NULL) { // 找到random指向的节点在链表中的位置 struct Node* temp = head; int random_index = 0; while (temp != cur->random) { temp = temp->next; random_index++; } hash[index]->random = hash[random_index]; } cur = cur->next; index++; } return hash[0]; }

优缺点:

  • 优点:思路简单直观
  • 缺点:需要 O(n) 的额外空间,且寻找random索引需要 O(n) 时间

关键点总结

  1. 插入复制节点:这是处理随机指针的关键,使得每个原节点后面都跟着它的复制节点
  2. 随机指针设置:利用 cur->random->next 找到对应的复制节点
  3. 链表分离:仔细处理指针,确保原链表恢复,复制链表正确连接
  4. 边界情况:处理空链表、单个节点等情况

扩展思考

如果链表有环怎么办?

如果原链表有环,这种方法仍然有效,因为:

  1. 插入复制节点后,环的大小会翻倍
  2. 设置随机指针时,逻辑不变
  3. 分离链表时,仍然可以正确分离

如果要求不修改原链表?

可以使用哈希表法,但空间复杂度会变为 O(n)。

应用场景

这种深拷贝带有随机指针的链表在以下场景中有应用:

  1. 对象序列化:深度复制复杂对象结构
  2. 图算法:复制带有随机边的图结构
  3. 数据库:复制关联数据结构
  4. 游戏开发:复制游戏对象及其关联关系

总结

随机链表的复制是一个经典的链表问题,考察了对指针操作和链表结构的深入理解:

  1. 核心技巧:三步法(插入→设置随机指针→分离)
  2. 关键洞察:通过在原节点后插入复制节点,可以轻松找到对应的随机指针目标
  3. 指针操作:需要仔细处理指针,避免内存泄漏或指针错误
  4. 效率优化:O(n) 时间复杂度和 O(1) 空间复杂度(不包括结果)

掌握这种解法对于处理复杂的链表结构和指针操作非常有帮助。

Read more

jdk 17 下载

可从 Oracle 官方 JDK 17 下载页 直接获取适用于 Windows、macOS、Linux 的 JDK 17 安装包Oracle,链接:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.htmlOracle 下载方式(按系统选择) 系统推荐下载链接备注WindowsWindows x64 安装包Oracle双击运行安装,适合大多数用户macOS IntelmacOS x64 DMGOracle直接安装macOS Apple SiliconmacOS arm64 DMGOracleM1/M2 芯片适用Linux x64Linux x64 压缩包Oracle解压后配置环境变量Linux ARM64Linux arm64 压缩包Oracle树莓派等设备适用 安装与验证 1. 下载 对应系统安装包。 2.

By Ne0inhk
使用飞算JavaAI搞定学生管理系统

使用飞算JavaAI搞定学生管理系统

标签<#JavaAI 飞算 JavaAI 的开发流程颠覆了我对传统开发的认知,整个过程就像和一位经验丰富的架构师实时协作,一下是我对开发学生管理系统的一些理解余流程操作 项目初始化阶段:在打开飞算 JavaAI 后,我创建了名一个"JavaProject" 的新项目,AI自动生成了基础的项目结构,包括IDEA配置文件夹、src 源代码目录、SQL文件夹和核心的 pom.xml 文件。这一步省去了传统开发中手动配置 Maven、设置项目结构的繁琐过程。 这里我自己的实操SQL数据库导入不了 但是在返回代码生成部分,表格设计这一块会有一个自动表格设计,在这里能帮你连接到数据库,后续的JavaAI就能按照这个数据库进行快速创作。 需求定义阶段:在飞算 JavaAI 的智能引导模块,输入了详细的需求,要飞算avaAI开发一个学生成绩管理系统,包含学生信息管理、课程管理、成绩录入、成绩统计分析、数据导出等功能,采用 SpringBoot 框架,MySQL 数据库。让我惊讶的是,

By Ne0inhk

OpenClaw到底是什么?3分钟搞懂AI圈的这些“黑话“

OpenClaw到底是什么?3分钟搞懂AI圈的这些"黑话" 你是不是也经常听到这些词:RAG、MCP、Skills、AI Agent… 每次看到都觉得似懂非懂,却又不好意思问? 今天,我们就用最通俗的话,把这些概念一次性讲清楚! 写在前面 最近刷到一个视频,讲的是 OpenClaw(clawdbot) 这个项目。 说实话,第一反应也是懵的:这又是个啥? 但仔细看完后发现,这个项目其实是个很好的"教材"——它把现在AI圈最火的几个技术串在了一起。搞懂了它,你也就搞懂了整个AI技术栈的底层逻辑。 那么,OpenClaw到底是个啥? 简单说,它就是一个聪明的AI助手框架,把各种AI能力(记忆、检索、工具调用)整合在一起,让AI真的能"干活",而不只是聊天。 先搞清楚一个概念:什么是"

By Ne0inhk
AI的提示词专栏:多语言 Prompt,中文、英文、日文混写的实践

AI的提示词专栏:多语言 Prompt,中文、英文、日文混写的实践

AI的提示词专栏:多语言 Prompt,中文、英文、日文混写的实践 本文围绕多语言 Prompt(中文、英文、日文混写)展开全面实践探讨,先阐述其打破跨语言信息壁垒、提升专业场景精准度、适配多语言用户需求的核心价值,再分析中、英、日三种语言特性对 Prompt 编写的影响,接着提出语言标识清晰、核心需求统一、文化适配性的基础编写原则与语言切换逻辑设计、术语对齐、混合语言优先级设定的进阶技巧。文中结合跨境电商产品文案生成、国际学术论文摘要撰写、跨国企业会议纪要制作三大行业实战案例,展示多语言 Prompt 的应用方法,还针对模型语言混淆、术语翻译偏差、文化适配不当等常见问题给出解决方案,最后总结核心要点并展望自动化语言适配、多模态多语言融合等未来趋势,为全球化场景下多语言 Prompt 的使用提供全面指导。 人工智能专栏介绍     人工智能学习合集专栏是 AI 学习者的实用工具。它像一个全面的 AI 知识库,把提示词设计、AI 创作、智能绘图等多个细分领域的知识整合起来。

By Ne0inhk