算法题解记录27+++随机链表的复制(百日筑基)

2024-06-02 00:52

本文主要是介绍算法题解记录27+++随机链表的复制(百日筑基),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、题目描述:

        题目难度:中等

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

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

        例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

        返回复制链表的头节点。

        用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 0 <= n <= 1000
  • -10^4 <= Node.val <= 10^4
  • Node.random 为 null 或指向链表中的节点。

二、解题准备

        1.了解题意:

        题目要求拷贝链表,需要注意的是,本题链表的定义和普通链表不同,本题链表的节点,除了val域和next域,还有一个random域,随机指向本链表的某一节点【但是这链表肯定不是环形链表】

        深度拷贝,要求复制出一个一模一样的链表,这种链表,除了val域一样,next域指向新节点外,random域指向的节点,不能是原先链表的节点。

        用3元式表示一个节点【val,next,random】,其中,@x表示指向第x个节点,这个x从0开始。

        举个例子,我们有一个链表【1,@1,@NULL】,【4,@2,@3】,【10,@3,@0】,【7,@NULL,@NULL】

        这个链表类似于下图【不懂绘图,勉强看吧】

        我们需要的新链表,要与这个一模一样,但是是下图这样的:

        至此你应该理解了题意。

        

        2.基本操作:

        本题涉及链表的创建和增加。

        3.基础原理:

        对于链表题目,要记住最基础的一个方法(如何遍历链表):

        如果是使用迭代while,那么,基础的迭代语法就是:

Node temp = head; // 防止遍历结束后,丢失读取链表的方法
while(temp!=null){print(temp.val); // 伪代码,访问temp的数据temp = temp.next;
}
// 结束条件:最终temp指向第一个null“节点”

三、解题思路

        首先考虑,如果去除random域,那么如何复制一份链表?

        1.简化思路:最基本的链表拷贝方法

        拷贝一个链表,其实是拷贝链表的val域,以及链表节点之间的相互关系。

        比如上面说的例子【1,4,10,7】。我们知道链表间的关系就是1指向4,4指向10,10指向7。

        由于链表的next域自带这种联系方式,所以拷贝的时候,不必把相互关系存储起来,而是在遍历时,一边拷贝即可。

        也就是说,我们首先创建一个对象res,指向head(或者用哑节点也行,无所谓)

        然后,遍历原链表,同时不断创建新结点。

        代码如下:

        Node res = new Node(head.val); // 指向头节点temp = head.next; // 如果指向head,那么while中会拷贝len次,而res已经拷贝一次real = res; // 指向头节点,避免丢失res// 一直迭代到temp指向第一个null节点。while(temp!=null){// 为什么用next?因为如果用real本身,会丢失联系。// 解释:real原先是一个null节点,你new之后,前面的联系就断了real.next = new Node(temp.val);temp = temp.next;real = real.next;}return res; // res就是我们要的答案

        得到拷贝基础链表的方法后,开始考虑本题的random域链表。

        我们首先要确定一件事:

        拷贝一个数据结构,就是拷贝元素值,以及元素之间的相互关系。

        如果进行一遍“简化思路”,那么,除了random域的元素关系,其它的元素关系都被拷贝了。

        所以,只需要考虑如何把random域的元素关系拷贝即可。

        2.思路:存储random域的元素关系

        如题,由于random是随机指向某个节点的,而链表不支持随机访问,也不能知道某个节点,在整个链表里,排在第几个元素。

        所以,我们需要用一个结构,将链表节点之间联系起来。

        比较简单地,自定义一个Class,有一个Node和一个int属性,头节点head是0,之后的按顺序排序。

        这里有一个问题:我们可以知道原链表的节点关系,但是无法映射到新链表中。

        解释:新链表是新对象,占用新内存,所以与原节点不能用“==”判同。

        因此,这个Class类,需要有两个Node、一个int属性,与上述类似,不过此时,两个Node,一个存储原链表的节点,另一个存储现在链表的节点。【在后续应用中,甚至不用int属性】

        此时,拷贝random域有方法了。解释:

        我们在浅度拷贝(即没考虑random域)时,将Class类填满。

        然后进行第二次遍历,这次遍历,从head开始,把原链表的每一个节点的random域找到,然后在Class中对应的新Node,即可深度拷贝。

        新问题:这种Class结构,需要花费很多资源,遍历起来也比较麻烦。

        解决方案:用HashMap。

四、解题难点分析

        无。

五、代码【HashMap】

class Solution {public Node copyRandomList(Node head) {// 空节点直接返回if(head == null){return null;}// 存储新、旧节点间关系Map<Node, Node> maps = new HashMap<>();Node temp = head.next;Node res, real;res = new Node(head.val);res.next = head.next;// 浅度拷贝real = res;maps.put(head, res);while(temp!=null){real.next = new Node(temp.val);maps.put(temp, real.next);temp = temp.next;real = real.next;}// 拷贝random域temp = head;real = res;while(temp!=null){if(temp.random != null){// 非空,则通过原链表的random域,映射到新链表的random域real.random = maps.get(temp.random);}temp = temp.next;real = real.next;}return res;}
}

        已经很久没更新百日筑基的内容了,一方面是这段时间忙于实训,需要重新学习、复习很多基础的框架知识,另一方面,算法题的刷题出现了瓶颈,对于回溯算法、贪心算法和图论,我已经觉得有点力不从心,虽然这些题目不难,但是由于自身知识储备不足,学起来比较吃力。

        不过好在,我已经大概刷了60余题,写百日筑基系列的基础,至少也到了下一个月,所以还是不担心缺少内容创作。这段文字写得很乱,也不知道有没有人在看这个系列,不过我会坚持下去的,积累就是人生漫漫长路的解决方案啊。

以上内容即我想分享的关于力扣热题27的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

这篇关于算法题解记录27+++随机链表的复制(百日筑基)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1022591

相关文章

国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)

《国内环境搭建私有知识问答库踩坑记录(ollama+deepseek+ragflow)》本文给大家利用deepseek模型搭建私有知识问答库的详细步骤和遇到的问题及解决办法,感兴趣的朋友一起看看吧... 目录1. 第1步大家在安装完ollama后,需要到系统环境变量中添加两个变量2. 第3步 “在cmd中

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

通过Python脚本批量复制并规范命名视频文件

《通过Python脚本批量复制并规范命名视频文件》本文介绍了如何通过Python脚本批量复制并规范命名视频文件,实现自动补齐数字编号、保留原始文件、智能识别有效文件等功能,听过代码示例介绍的非常详细,... 目录一、问题场景:杂乱的视频文件名二、完整解决方案三、关键技术解析1. 智能路径处理2. 精准文件名

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

linux如何复制文件夹并重命名

《linux如何复制文件夹并重命名》在Linux系统中,复制文件夹并重命名可以通过使用“cp”和“mv”命令来实现,使用“cp-r”命令可以递归复制整个文件夹及其子文件夹和文件,而使用“mv”命令可以... 目录linux复制文件夹并重命名我们需要使用“cp”命令来复制文件夹我们还可以结合使用“mv”命令总

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明