【刷题之路Ⅱ】LeetCode 138. 复制带随机指针的链表

news2024/11/28 12:36:58

【刷题之路Ⅱ】LeetCode 138. 复制带随机指针的链表

  • 一、题目描述
  • 二、解题
    • 难点分析
    • 方法——插入拷贝节点
        • 2、将拷贝节点插入到原节点的后面
        • 3、复制原节点的random到拷贝节点中
        • 4、将拷贝节点尾插到新链表中并恢复原链表的结构

一、题目描述

原题连接: 138. 复制带随机指针的链表
题目描述:
给你一个长度为 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
-104 <= Node.val <= 104
Node.random 为 null 或指向链表中的节点。

二、解题

难点分析

可能有的朋友在读完这一大串的题目后可能就感觉快晕厥过去了,可能还不知道题目的要求是什么。题目的要求其实很简单,就是要你复制一个同样结构链表,即每个节点的next和random的指向都一样,例如:
在这里插入图片描述
有人可能就会想这还不简单,我遍历所有的原节点,然后每次都开辟一个新节点,再将原节点的next和random赋值到新节点中不就行了吗。
如果你是这样想的,那你就大大的理解错了题目了,如果你真的像上面所说的了,那我们最终完成的效果就如下图所示:
在这里插入图片描述
原因当然也很明显,如果我们像上面所说的去做,那我们开辟出来的新节点的next和random所执行的还是原来链表中的节点。而题目要求我们的是开辟出一个新的链表,链表中的节点的next和random连接到的也是新的节点。

而如果该题目的链表没有random这个指针,其实要做起来并没有什么难度,我们只需要遍历原链表的所有节点,对于每一个节点都开辟一个新的节点,将原节点的val复制到新节点,并想办法记录到上一次开辟的节点,将上一次开辟的节点的next指向这次开辟的节点即可,代码并不难,很容易就能写得出:

struct Node *copyList(struct Node *head) {
    if (NULL == head) {
        return NULL;
    }
    struct Node *copyhead = NULL;
    struct Node *cur = head;
    struct Node *pre = NULL; // 记录前一个节点
    while (cur) {
        struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
        newNode->val = cur->val;
        if (NULL == newhead) {
            newhead = newNode;
            pre = newNode;
        } else {
            pre->next = newNode;
            pre = newNode;
        }
    }
    pre->next = NULL;
    return newhead;
}

但这题难就难在多了个random节点,因为当我们想要复制random指针时,我们很大可能并没有办法知道random指向的节点的地址。例如我们在复制某个节点的random时,它的random可能指向的是前几个开辟的节点,但我们最多只能记录前一个节点的地址,所以我们就不能直接的知道random指向的节点的地址:
在这里插入图片描述
当然也有可能是某个节点的random指向的节点还没开辟出来:
在这里插入图片描述
那么这一题到底应该怎么解呢?
接下来就让我们正式开始解题吧。

方法——插入拷贝节点

这一题虽然看起来难,但只要我们把它细分成三个主要步骤,就会发现这三个步骤其实考察的还是一些链表的基本操作,并没有多难的。

2、将拷贝节点插入到原节点的后面

其实我们可以先把复制好的节点插入到原节点的后面:
在这里插入图片描述
这样其实就可以做到通过原节点作为一个“桥梁”从而将我们拷贝的链表中的节点的random指针指向相应的节点。
具体该怎么做我们放到下一步在介绍,而我们这一步先完成将拷贝的节点插入到原节点的后面。
这个操作其实并不难完成,这其实是和我们链表的随机插入是一样的,我们可以用一个cur指针遍历原链表的每一个节点,每遍历到一个节点我们就相应的开辟出一个新节点:
在这里插入图片描述
开辟好节点后我们就先让新节点的next指向cur的下一个节点,再让cur的next指向新节点即可,完事后再让cur等于newnode的next即可。直到cur等于空,我们就完成了拷贝节点的插入:
在这里插入图片描述
具体的代码如下:

struct Node* copyRandomList(struct Node* head) {
    struct Node *cur = head;
    // 第一步,先拷贝原节点,并将拷贝的节点连接到源节点后面
    while (cur) {
        struct Node *CopyNode = (struct Node*)malloc(sizeof(struct Node));
        CopyNode->val = cur->val;

        // 将拷贝的节点连接到原节点的后面
        CopyNode->next = cur->next;
        cur->next = CopyNode;
        cur = CopyNode->next;
    }
}

3、复制原节点的random到拷贝节点中

接下来我们就可以通过我们上一步所搭建的“桥梁”来完成random指针的拷贝了。
通过上一步我们就可以通过原节点找到对应的拷贝节点了(即原节点的next):
在这里插入图片描述
相同的道理,其实我们也可以通过原节点来找到对应的拷贝节点的random所指向的节点,因为我们可以通过原节点来找到原链表中原节点的random指向的节点,那当我们找到了这个节点,这个节点的next不就是对应的拷贝链表中的random指向的节点了吗?例如:
在这里插入图片描述
所以,假设我们使用一个cur指针来再次遍历原链表,我们想要拷贝新链表的random的指向就只需要执行cur->next->random = cur->random-next即可:
在这里插入图片描述
有了这个思路,那我们写起代码来也就没有问题了:

struct Node* copyRandomList(struct Node* head) {
    struct Node *cur = head;
    // 第一步,先拷贝原节点,并将拷贝的节点连接到源节点后面
    while (cur) {
        struct Node *CopyNode = (struct Node*)malloc(sizeof(struct Node));
        CopyNode->val = cur->val;

        // 将拷贝的节点连接到原节点的后面
        CopyNode->next = cur->next;
        cur->next = CopyNode;
        cur = CopyNode->next;
    }

    // 第二步,拷贝原节点的random到拷贝的节点中
    cur = head;
    while (cur) {
        struct Node *copy = cur->next;

        // 拷贝random
        if (NULL == cur->random) { // random指向空的情况
            copy->random = NULL;
        } else {
            copy->random = cur->random->next;
        }
        cur =copy->next;
    }
}

4、将拷贝节点尾插到新链表中并恢复原链表的结构

最后一步就是将我们拷贝好的新节点尾插到新的链表当中,然后再恢复原链表的结构。
尾插其实和普通的链表的尾插逻辑是一样的,而恢复原链表的结构其实也和链表的随机删除节点的逻辑是一样的。
完成这个操作我们需要很多个指针,具体如下图:
在这里插入图片描述
首先我们还是需要一个cur指针来遍历原链表中的节点,然后我们需要一个copy指针保存我们待尾插的节点(即cur的next),而为了在尾插完后我们还能找到原链表的下一个节点,我们需要一个next指针来保存我们原链表的下一个节点(即copy的next)。而为了方便尾插,我们还需要一个tail指针来记录新节点的尾节点,每次插入一个新节点,我们就得让tail往后走一步。
完成这些后,我们直接返回newhead即可。
代码其实并不难,如下:

struct Node* copyRandomList(struct Node* head) {
    struct Node *cur = head;
    // 第一步,先拷贝原节点,并将拷贝的节点连接到源节点后面
    while (cur) {
        struct Node *CopyNode = (struct Node*)malloc(sizeof(struct Node));
        CopyNode->val = cur->val;

        // 将拷贝的节点连接到原节点的后面
        CopyNode->next = cur->next;
        cur->next = CopyNode;
        cur = CopyNode->next;
    }

    // 第二步,拷贝原节点的random到拷贝的节点中
    cur = head;
    while (cur) {
        struct Node *copy = cur->next;

        // 拷贝random
        if (NULL == cur->random) {
            copy->random = NULL;
        } else {
            copy->random = cur->random->next;
        }
        cur =copy->next;
    }

    // 第三步,将拷贝的节点尾插到新链表,并恢复原链表的结构
    cur = head;
    struct Node *newhead = NULL;
    struct Node *tail = NULL; // 记录新链表的为尾节点
    while (cur) {
        struct Node *copy = cur->next;
        struct Node *next = copy->next;

        // 将从copy节点头插到新链表
        if (NULL == newhead) {
            newhead = copy;
            tail = copy;
        } else {
            tail->next = copy;
            tail = tail->next;
        }

        // 恢复原链表的结构
        cur->next = next;
        cur = next;
    }
    return newhead;
}```


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/470833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

考研拓展:汇编基础

一.说明 本篇博客是基于考研之计算机组成原理中的程序机器级代码表示进行学习的&#xff0c;并不是从汇编语言这一门单独的课程来学习的&#xff0c;涉及的汇编语言知识多是帮助你学习考研之计算机组成原理中对应的考点。 二.相关寄存器 1.相关寄存器 X86处理器中有8个32位…

【三十天精通Vue 3】第二十天 Vue 3的性能优化详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue3 性能优化的概念1.1 为什么需要性能优化1.2 性能优化…

基于dsp+fpga+AD+ENDAC的半导体运动台高速数据采集电路仿真设计(四)

整个调试验证与仿真分析分三个步骤&#xff1a;第一步是进行 PCB 检查及电气特性测试&#xff0c;主 要用来验证硬件设计是否正常工作&#xff1b;第二步进行各子模块功能测试&#xff0c;包括高速光纤串行 通信的稳定性与可靠性测试&#xff0c; A/D 及 D/A 转换特性测…

26从零开始学Java之如何对数组进行排序与二分查找?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家讲解了数组的扩容、缩容及拷贝方式。接下来在今天的文章中&…

深眸科技|深度学习、3D视觉融入机器视觉系统,实现生产数智化

随着“中国制造2025”战略加速落实&#xff0c;制造业生产线正在加紧向智能化、自动化和数字化转型之路迈进。而人工智能技术的兴起以及边缘算力持续提升的同时&#xff0c;机器视觉及其相关技术也在飞速发展&#xff0c;并不断渗透进工业领域&#xff0c;拓展应用场景的同时&a…

Apache Druid中Kafka配置远程代码执行漏洞(MPS-2023-6623)

漏洞描述 Apache Druid 是一个高性能的数据分析引擎。 Kafka Connect模块曾出现JNDI注入漏洞(CVE-2023-25194)&#xff0c;近期安全研究人员发现Apache Druid由于支持从 Kafka 加载数据的实现满足其利用条件&#xff0c;攻击者可通过修改 Kafka 连接配置属性进行 JNDI 注入攻…

软件架构中间件技术

中间件的定义 其实中间件是属于构件的一种。是一种独立的系统软件或服务程序&#xff0c;可以帮助分布式应用软件在不同技术之间共享资源。 我们把它定性为一类系统软件&#xff0c;比如我们常说的消息中间件&#xff0c;数据库中间件等等都是中间件的一种体现。一般情况都是…

减少 try catch ,可以这样干

软件开发过程中&#xff0c;不可避免的是需要处理各种异常&#xff0c;就我自己来说&#xff0c;至少有一半以上的时间都是在处理各种异常情况&#xff0c;所以代码中就会出现大量的try {...} catch {...} finally {...}代码块&#xff0c;不仅有大量的冗余代码&#xff0c;而且…

d3.js学习笔记①创建html文档

本人之前从未学过HTML、CSS、JavaScript&#xff0c;然而我导是做前端的&#xff0c;要求我必须在三周内掌握d3.js&#xff0c;我只能从0学起并以此记录自己的学习过程。 首先对这三种语言有一个初步的认识&#xff1a;HTML是用于搭建网页框架&#xff0c;CSS是美化网页的&…

软件设计师考试——计算机网络、系统安全分析和设计部分

计算机网路 七层模型 OSI/RM七层模型 网络技术标准与协议 TCP协议 DHCP协议 DNS协议 计算机网络的分类——拓扑结构 按分布范围&#xff1a; 局域网城域网广域网因特网 按拓扑结构&#xff1a; 总线型星型环型 网络规划与设计 逻辑网络设计 物理网络设计 分层设计 IP地址…

VirboxLM-免服务版授权码,快速实现一机一码

一、产品介绍 ​ 授权码是由深盾科技开发的一款软件保护及授权管理产品 ​&#xff0c;一方面要保护软件代码不被逆向&#xff0c;另一方面要控制软件的授权使用。软件用户只需要输入授权码&#xff08;由数字和字母组成的一串字符&#xff09;&#xff0c;激活授权码后即可使…

这年头,谁还在「贩卖」生活方式?

【潮汐商业评论/原创】 “我已经很久没有追寻过品牌购物了”Anna如是说。 如今的Anna对商品的选择往往会考虑性价比以及简洁的外观&#xff0c;去品牌化、简单已经成为其新的生活方式。 日本作者三浦展在《第四消费时代》一书中提到&#xff0c;在第三消费社会&#xff0c;新…

Java版本企业工程项目管理系统平台源码(三控:进度组织、质量安全、预算资金成本、二平台:招采、设计管理)

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#…

Android 获取奔溃crash的日志(adb logcat或者dropbox)

1.通过adb logcat 来获取&#xff1a; 使用场景&#xff1a;测试或者开发小伙伴 抓取。 先执行adb logcat -c 清理缓存日志 接着&#xff0c;抓取当前时间段开始的日志: adb logcat -v time >D:/crash.log 也可以抓取指定进程的日志&#xff1a; adb logcat -v time | fi…

利用POSIX多线程API函数进行多线程开发

本书文字内容源自 <<linux C/C服务器开发实践>> 支持正版图书&#xff0c;测试代码根据测试目的&#xff0c;可自行修改测试。 前言 在用POSIX多线程API函数进行开发之前&#xff0c;我们首先要熟悉这些API函数。常见的与线程有关的基本API函数见下表 使用这些A…

亚马逊云科技综合解决方案助力美的智能化,成本节省30%

很多人都有和客服打交道的体验&#xff0c;而这种体验大概率不佳&#xff0c;人工客服迟迟不应&#xff0c;解答问题也不精准&#xff0c;糟糕的客服体验对于面向消费者的企业来说亦是一大难题&#xff0c;严重者甚至会导致客户流失、评价滑坡等后果&#xff0c;作为知名科技电…

FileInputStream.read和FileChannel.read的区别

FileChannel怎么来的 FileChannel channel new FileInputStream("").getChannel() FileChannel的read()方法 channel.read(byteBuffer) 实现类FileChannelImpl 首先映入眼帘的就是非常熟悉的synchronized关键字&#xff0c; private final Object positionLock ne…

宏基因组组装 | 就现在!做出改变!!

微生态研究的核心难点是什么&#xff01; 基因组组装&#xff01; 从宏基因组数据中组装获得细菌的完整基因组&#xff08;complete MAGs&#xff09;是微生物组研究的长期目标&#xff0c;但基于NGS的宏基因组测序和组装方法是无法实现完整的细菌基因组组装的。即便是红极一…

appium-app测试-环境搭建手机和adb设置

1、手机设置&#xff08;Android手机-readMI k50&#xff09;&#xff1a; 1.1开发者模式设置 入口&#xff1a;设置–我的设备–全部参数与信息&#xff1b; 连续点击MIUI版本7下&#xff0c;进入开发者模式 1.2、开发者选项设置 入口&#xff1a;设置–更多设置–开发者选项…

np.convolve(x,h, mode=‘##‘)的使用

用法&#xff1a; np.convolve(a,v,mode) a代表卷积数据&#xff0c;v卷积核大小&#xff0c;mode卷积方式&#xff0c;mode卷积方式有三种 same full valid mode可能的三种取值情况&#xff1a; full’ 默认值&#xff0c;返回每一个卷积值&#xff0c;长度是NM-1,在卷积的…