【面试经典150 | 链表】随机链表的复制

news2024/11/26 23:35:49

文章目录

  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:哈希表+递归
    • 方法二:哈希表
    • 方法三:迭代+拆分节点
  • 写在最后

Tag

【递归】【迭代】【链表】


题目来源

138. 随机链表的复制


题目解读

对一个带有随机指向的链表进行深拷贝操作。


解题思路

本题一共有三种解法,分别是:

  • 哈希表+递归;
  • 哈希表;
  • 迭代+节点拆分。

前两种方法都需要用到哈希表这种基本的数据结构,哈希表中存放的数据都是源节点与深拷贝后的节点这样的键值对,但是一个(方法一)是使用递归进行完成深拷贝任务,而另一个(方法二)使用迭代来完成深拷贝工作,这两种方法属于常规解法了。

第三种解法比较巧妙,省去了哈希表,节省了空间,面试的时候能够清晰的答出该方法一定可以令面试官眼前一亮。

接下来具体看一看这三种解法。

方法一:哈希表+递归

如果是对一个普通的链表进行深拷贝操作,我们直接按照遍历的顺序创建链表即可。但是,本题中的链表有一个随机指针,指向链表中的随机的一个节点,如果还是按照链表顺序拷贝节点,当前拷贝的节点的随机节点可能还没有创建。

于是会有两种不同的思路,一是通过递归的方法创建需要的节点,具体地:

  • 维护一个哈希表 cacheNode 用来存放源节点与深拷贝后的节点;
  • 首先创建头结点的拷贝节点,并存入到哈希表中;
  • 接着递归建立新头结点的下一个节点与随机节点,也就是调用自身;
  • 最后返回 cacheNode[head]

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*, Node*> cacheNode;

    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return NULL;
        }    
        if (!cacheNode.count(head)) {
            Node* newHead = new Node(head->val);
            cacheNode[head] = newHead;
            newHead->next = copyRandomList(head->next);
            newHead->random = copyRandomList(head->random);
        }
        return cacheNode[head];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

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


方法二:哈希表

直接使用根据每个节点拷贝出新的节点,存放到哈希表中,然后根据原链表的指向关系更新新节点的指向关系。

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*, Node*> cacheNode;

    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return NULL;
        }    
        
        Node* curr = head;
        while (curr != NULL) {
            cacheNode[curr] = new Node(curr->val);
            curr = curr->next;
        }

        curr = head;
        while (curr != NULL) {
            cacheNode[curr]->next = cacheNode[curr->next];
            cacheNode[curr]->random = cacheNode[curr->random];
            curr = curr->next;
        }
        return cacheNode[head];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

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

方法三:迭代+拆分节点

方法三和方法二类似,都是先将原链表拷贝一次,只是本方法是先将拷贝后的链表先连在原链表后,接着就是巧妙的连接与拆分方法,接下来以图示的方式进行分析:

原链表为 A->B->C

深拷贝节点并连接next指针

我们先在原链表的节点之后深拷贝一个新的节点并连接,生成一个新的链表 A->A'->B->B'->C->C'。实现的方法与在链表指定节点 node 之后插入一个新的节点类似:

  • 先深拷贝节点 node 得到新的节点 newNode
  • 再将拷贝得到的新节点 newNode 连接到 node->next
  • 最后将 node 连接到 newNode

连接random指针

现在需要解决拷贝后的链表的 random 指针指向问题,node 的下一个节点就是 newNodenewNoderandom 指针指向就是 noderandom 指向,我们就根据 node 来更新就可以。具体实现见代码。

切断两链表之间的联系

解决好 random 指针指向问题,就要切断两个链表之间的联系。具体实现见代码

实现代码

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return head;
        }

        // 深拷贝节点并连接next指针
        for (Node* node = head; node != NULL; node = node->next->next) {
            Node* newNode = new Node(node->val);
            newNode->next = node->next;
            node->next = newNode;
        }

        // 连接random指针
        for (Node* node = head; node != NULL; node = node->next->next) {
            Node* newNode = node->next;
            newNode->random = (node->random != NULL) ? node->random->next : NULL;
        }

        // 切断两链表之间的联系
        Node* newHead = head->next;
        for (Node* node = head; node != NULL; node = node->next) {
            Node* newNode = node->next;
            node->next = node->next->next;
            newNode->next = (newNode->next != NULL) ? newNode->next->next : NULL;
        }        
        return newHead;
    }
};

时间复杂度: O ( n ) O(n) O(n) n n n 是链表的长度。

空间复杂度: O ( 1 ) O(1) O(1),答案链表不算作额外的空间。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

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

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

相关文章

layui form表单 调整 label 宽度

这个可以调整所有label .layui-form-label {width: 120px !important; } .layui-input-block {margin-left: 150px !important; }情况是这样的&#xff0c;表单里有多个输入框&#xff0c;只有个别label 是长的&#xff0c;我就想调整一下个别长的&#xff0c;其它不变 <di…

小程序day02

目标 WXML模板语法 数据绑定 事件绑定 那麽問題來了&#xff0c;一次點擊會觸發兩個組件事件的話&#xff0c;該怎么阻止事件冒泡呢&#xff1f; 文本框和data的双向绑定 注意点: 只在标签里面用value“{{info}}”&#xff0c;只会是info到文本框的单向绑定&#xff0c;必须在…

1、循环依赖详解(一)

什么是循环依赖&#xff1f; 什么情况下循环依赖可以被处理&#xff1f; Spring是如何解决的循环依赖&#xff1f; 只有在setter方式注入的情况下&#xff0c;循环依赖才能解决&#xff08;错&#xff09; 三级缓存的目的是为了提高效率&#xff08;错&#xff09; 什么是循环…

在基于亚马逊云科技的湖仓一体架构上构建数据血缘的探索和实践

背景介绍 随着大数据技术的进步&#xff0c;企业和组织越来越依赖数据驱动的决策。数据的质量、来源及其流动性因此显得非常关键。数据血缘分析为我们提供了一种追踪数据从起点到终点的方法&#xff0c;有助于理解数据如何被转换和消费&#xff0c;同时对数据治理和合规性起到关…

Ajax学习笔记第8天

放弃该放弃的是无奈&#xff0c;放弃不该放弃的是无能&#xff0c;不放弃该放弃的是无知&#xff0c;不放弃不该放弃的是执着&#xff01; 【1. 聊天室小案例】 文件目录 初始mysql数据库 index.html window.location.assign(url); 触发窗口加载并显示指定的 url的内容 当前…

TSINGSEE青犀特高压输电线可视化智能远程监测监控方案

一、背景需求分析 特高压输电线路周边地形复杂&#xff0c;纵横延伸几十甚至几百千米&#xff0c;并且受所处地理环境和气候影响很大。传统输电线路检查主要依靠维护人员周期性巡视&#xff0c;缺乏一定的时效性&#xff0c;在巡视周期的真空期也不能及时掌握线路走廊外力变化…

AQS面试题总结

一&#xff1a;线程等待唤醒的实现方法 方式一&#xff1a;使用Object中的wait()方法让线程等待&#xff0c;使用Object中的notify()方法唤醒线程 必须都在synchronized同步代码块内使用&#xff0c;调用wait&#xff0c;notify是锁定的对象&#xff1b; notify必须在wait后执…

振弦式传感器读数模块VM5系列介绍

VM5系列是专门针对单线圈式振弦传感器研发&#xff0c;可完成传感器的线圈激励、频率读数、温度测量等工作&#xff0c;具有标准的 UART&#xff08;TTL/RS232/RS485&#xff09;和 IIC 数字接口、模拟量输出接口&#xff08;电压或电流&#xff09;&#xff0c;通过数字接口数…

【论文阅读笔记】GLM-130B: AN OPEN BILINGUAL PRE-TRAINEDMODEL

Glm-130b:开放式双语预训练模型 摘要 我们介绍了GLM-130B&#xff0c;一个具有1300亿个参数的双语(英语和汉语)预训练语言模型。这是一个至少与GPT-3(达芬奇)一样好的100b规模模型的开源尝试&#xff0c;并揭示了如何成功地对这种规模的模型进行预训练。在这一过程中&#xff0…

arcgis图上添加发光效果!

看完本文, 你可以不借助外部图片素材, 让你的图纸符号表达出你想要的光! 我们以之前的某个项目图纸为例,来介绍下让符号发光的技术! 第一步—底图整理 准备好栅格影像底图、行政边界的矢量数据,确保“数据合适、位置正确、边界吻合”。 确定好图纸的大小、出图比例、投…

食品企业数字孪生可视化管理平台,实现智慧轻工业高质量发展

如今&#xff0c;数字技术正在打破传统食品产业的边界&#xff0c;随着食品加工产业链不断进化为智慧体&#xff0c;数字孪生技术已经成了食品行业数字进阶的重要抓手。食品加工数字孪生工厂&#xff0c;通过应用数字孪生技术&#xff0c;将食品加工工厂的自动化生产线全过程进…

浏览器哪家强——PC端篇

今天的分享将围绕一个大家再熟悉不过的名称展开——浏览器。 根据百科给出的解释&#xff1a;浏览器是用来检索、展示以及传递Web信息资源的应用程序。通俗的说&#xff0c;浏览器就是一种阅读工具&#xff0c;类似记事本、word、wps&#xff0c;只不过后者阅读的是文本文档&am…

Linux0.11内核源码解析-malloc

malloc介绍 Linux内核版本0.11中的malloc.c文件实现了内存分配的功能。在这个版本的Linux内核中&#xff0c;malloc.c文件包含了内核级别的内存分配函数&#xff0c;用于分配和释放内核中的内存。这些函数可以帮助内核管理可用的内存&#xff0c;并允许内核动态地分配和释放内…

ajax-axios发送 get请求 或者 发送post请求带有请求体参数

/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */ !function(e,t){"object"typeof exports&&"object"typeof module?module.exportst():"function"typeof define&&define.amd?define([],t):"object"typeof export…

记一次大数据事故@用了很久的虚拟机环境突然不能联网了

记一次大数据事故用了很久的虚拟机环境突然不能联网了 背景 今天打开自己电脑上的虚拟机环境打算练习一下flink&#xff0c;结果发现vmware里虚拟机能正常开机&#xff0c;也能正常进图os&#xff0c;但是就是不能ping通主机&#xff0c;主机也不能ping通虚拟机 探查 1、…

绝缘检测原理和绝缘电阻计算方法

文章目录 简介绝缘检测功能绝缘检测原理绝缘电阻检测的常用方法不平衡电桥法 绝缘电阻绝缘电阻的计算 绝缘检测开启或关闭为什么根据 V1 &#xff1c; V2 或 V1 ≥ V2 判断是上桥臂并入电阻还是下桥臂并入电阻 简介 绝缘检测是判断动力&#xff08;正、负&#xff09;总线与外…

Maven本地配置获取nexus私服的依赖

场景 Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包&#xff1a; Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包_nexus maven-releases 允许deploy-CSDN博客 在上面讲的是…

【6】c++11新特性(稳定性和兼容性)—>Lambda表达式

基本用法 lambda表达式是c最重要也是最常用的特性之一&#xff0c;这是现代编程语言的一个特点&#xff0c;lambda表达式有如下的一些优点&#xff1a; &#xff08;1&#xff09;声明式的编成风格&#xff1a;就地匿名定义目标函数活着函数对象&#xff0c;不需要额外写一个命…

Ubuntu20.04安装CUDA、cuDNN、tensorflow2可行流程(症状:tensorflow2在RTX3090上运行卡住)

最近发现我之前在2080ti上运行好好的代码&#xff0c;结果在3090上运行会卡住很久&#xff0c;而且模型预测结果完全乱掉&#xff0c;于是被迫研究了一天怎么在Ubuntu20.04安装CUDA、cuDNN、tensorflow2。 1.安装CUDA&#xff08;包括CUDA驱动和CUDA toolkit&#xff0c;注意此…

【MySQL】MySQL的安装与配置环境变量(使其在控制台上使用)

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《MySQL》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&a…