【链表复习】C++ 链表复习及题目解析 (2)

news2024/11/23 13:17:51

目录

牛客 CM11 链表分割

牛客 OR36 之链表的回文结构

Leetcode 160. 相交链表

LeetCode 141. 环形链表

LeetCode 138. 复制带随机指针的链表


本文继续延续前文,为大家带来几道经典的链表中等难度的题目。

牛客 CM11 链表分割

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路:如果我们在原来的链表上进行操作非常麻烦,可以新建两个链表,分别包含大于x 的结点和小于x 的结点。最后将两个链表合并即可,只需要注意一个问题,就是不要让他们成环,前面结点的指针都会根据大于或者小于x 来进行修改,成环的点在于大于x 的结点组成的链表的最后一个结点的指向,一定要置空,否则会成环。

我的解法:

 /*
 struct ListNode {
     int val;
     struct ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };*/
 class Partition {
 public:
     ListNode* partition(ListNode* pHead, int x) {
         ListNode* pBiggerHead = new ListNode(-1);//哨兵头结点
         ListNode* pLowerHead = new ListNode(-1);//哨兵头结点
         ListNode* pBiggerCurr = pBiggerHead;
         ListNode* pLowerCurr = pLowerHead;
         
         while(pHead){
             if(pHead->val < x){
                 pLowerCurr->next = pHead;
                 pLowerCurr = plc->next;
            }else{
                 pBiggerCurr->next = pHead;
                 pBiggerCurr = pBiggerCurr->next;
            }
             pHead = pHead->next;
        }
         pLowerCurr->next = pBiggerHead->next;
         pBiggerCurr->next = nullptr;
         return pLowerHead->next;
    }
 };

牛客 OR36 之链表的回文结构

描述:

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

测试样例:

 1->2->2->1
 返回:true

思路:首先找到中间结点;将中间结点后半部分倒置;分别从头结点和尾结点向中间遍历,检测在达到中间时刻之间val的值是否都相等。所以我们需要用上之前写的题目,先找到中间结点,然后从中间结点开始逆置,就会形成如下的形状。

 1 -> 2 -> 3 <- 2 <- 1
 /*
 struct ListNode {
     int val;
     struct ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
 };*/
 class PalindromeList {
   public:
     struct ListNode* middleNode(struct ListNode* head) {
         ListNode* dummy = new ListNode(-1);
         dummy->next = head;
         struct ListNode* fast = dummy;
         struct ListNode* slow = dummy;
         while (fast) {
             slow = slow->next;
             if (fast->next)
                 fast = fast->next->next;
             else
                 return slow;
        }
         return slow;
    }
     struct ListNode* reverseList(struct ListNode* head) {
         struct ListNode* cur = head;
         struct ListNode* pre = nullptr;
         struct ListNode* next = nullptr;
         while (cur) {
             next = cur->next;
             cur->next = pre;
             pre = cur;
             cur = next;
        }
         return pre;
    }
     bool chkPalindrome(ListNode* A) {
         ListNode* midNode = middleNode(A);
         ListNode* tailNode = reverseList(midNode);
         while(A != midNode){
             if(A->val != tailNode->val) return false;
             A = A->next;
             tailNode = tailNode->next;
        }
         return true;
    }
 };

Leetcode 160. 相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

思路:本题的难度在于两个单链表的长度未知,而且关系未知,就是有可能等长,有可能不等长。我们可以知道如果A链表长度为a,B链表长度为b,我们可以先遍历一遍,寻找到A 和B 链表的长度的差值 |a-b|,然后让长的先走差值,然后再开始比较。

我的解法1:

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
         if(!headA || !headB) return nullptr;
         int lengthA = 0;
         int lengthB = 0;
         ListNode* currA = headA;
         ListNode* currB = headB;
         while(currA){
             lengthA++;
             currA = currA->next;
        }
         while(currB){
             lengthB++;
             currB = currB->next;
        }
         currA = headA;
         currB = headB;
         if(lengthA > lengthB){
             //A 先走
             int dif = lengthA - lengthB;
             while(dif--){
                 currA = currA->next;
            }
        }else if(lengthA < lengthB){
             int dif = lengthB - lengthA;
             while(dif--){
                 currB = currB->next;
            }
        }
         int less = lengthA > lengthB ? lengthB : lengthA;
         while(less--){
             if(currA == currB){
                 return currA;
            }else{
                 currA = currA->next;
                 currB = currB->next;
            }
        }
         return nullptr;
    }
 };

我的解法2:

上一种解法总体来说还是比较繁琐的,我们可以采用更简单的双指针法,可以假设A 长度为m, B 长度为n,如果相交,A 和 B相交片段长度为 x, 不相交的片段长度分别为a 和b。

那么如果我们采用双指针法:

当链表 headA 和 headB 都不为空时,创建两个指针 pA和 pB,初始时分别指向两个链表的头节点 headA 和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:

  • 每步操作需要同时更新指针 pA 和 pB。

  • 如果指针 pA不为空,则将指针 pA 移到下一个节点;如果指针 pB不为空,则将指针 pB 移到下一个节点。

  • 如果指针 pA为空,则将指针 pA 移到链表 headB的头节点;如果指针 pB 为空,则将指针 pB 移到链表 headA 的头节点。

  • 当指针 pA 和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null。

那么如果二者相交,则 m = a + x, n = b + x。pA 和 pB 一定在走过 a + b + x 长度时相等。如果二者不相交,则一定不会出现相等(可以分类讨论)。

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
         ListNode* pA = headA;
         ListNode* pB = headB;
         while(pA != nullptr || pB != nullptr){
             if(pA == pB) return pA;
             if(pA == nullptr){
                 pA = headB;
            }else{
                 pA = pA->next;
            }
             if(pB == nullptr){
                 pB = headA;
            }else{
                 pB = pB->next;
            }
        }
         return nullptr;
    }
 };

LeetCode 141. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false

思路:快慢指针,快指针一次走两步,慢指针一次走一步,如果没环,快指针会先走到链表尾,如果有环,快指针会先入环,而后慢指针入环,因为二者步幅差1,所以最终一定会相遇。

我的解法:

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     bool hasCycle(ListNode *head) {
         if(!head || !head->next) return false; //如果头节点和头节点的下一个是空,那么肯定不会成环
         ListNode* fast = head->next;
         ListNode* slow = head;
         while(fast){
             if(fast == slow){
                 return true;
            }
             if(fast->next){
                 fast = fast->next->next;;
            }else{ return false; }
             slow = slow->next;
        }
         return false;
    }
 };

LeetCode 138. 复制带随机指针的链表

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

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

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

返回复制链表的头节点。

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

  • val:一个表示 Node.val 的整数。

  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

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

思路1:

本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。

具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。

在实际代码中,我们需要特别判断给定节点为空节点的情况。

复杂度分析

时间复杂度:O(n),其中 n 是链表的长度。对于每个节点,我们至多访问其「后继节点」和「随机指针指向的节点」各一次,均摊每个点至多被访问两次。

空间复杂度:O(n),其中 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 == nullptr){
             return nullptr;
        }    
         if(!cacheNode.count(head)){
             Node* headNew = new Node(head->val);
             cacheNode[head] = headNew;
             headNew->next = copyRandomList(head->next);
             headNew->random = copyRandomList(head->random);
        }
         return cacheNode[head];
    }
 };

我的解法2:

注意到方法一需要使用哈希表记录每一个节点对应新节点的创建情况,而我们可以使用一个小技巧来省去哈希表的空间。

我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A′→B→B′→C→C′ 。对于任意一个原节点 S,其拷贝节点 S′ 即为其后继节点。

这样,我们可以直接找到每一个拷贝节点 S′ 的随机指针应当指向的节点,即为其原节点 SSS 的随机指针指向的节点 T 的后继节点 T‘ 。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。

当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。

复杂度分析

时间复杂度:O(n),其中 n 是链表的长度。我们只需要遍历该链表三次。读者们也可以自行尝试在计算拷贝节点的随机指针的同时计算其后继指针,这样只需要遍历两次。 空间复杂度:O(1)。注意返回值不计入空间复杂度。

 /*
 // 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 == nullptr){
             return nullptr; //如果为空则不讨论
        }
         for(Node* node = head; node != nullptr; node = node->next->next){
             Node* newNode = new Node(node->val);
             nodeNew->next = node->next;
             node->next = nodeNew; //创建新节点在原节点之后
        }
         for(Node* node = head; node != nullptr; node = node->next->next){
             Node* nodeNew = node->next;
             newNode->random = (node->random != nullptr) ? node->random : nullptr;
             //修改新链表random的指向
        }
         Node* headNew = head->next;
         for(Node* node = head; node != nullptr; node = node->next){
             Node* nodeNew = node->next; 
             node->next = node->next->next; //修改原链表的next。
             nodeNew->next = (nodeNew->next != nullptr) ? nodeNew->next->next : nullptr; 
             //修改新链表的next指向
        }
         return headNew;
    }
 };

 

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

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

相关文章

探索iOS之Metal标准库

Metal标准库包括&#xff1a;通用函数、整型函数、关系函数、数学函数、矩阵运算、SIMD运算、几何函数、纹理函数等。接下来让我们走进Metal标准库的世界。 1、通用函数 通用函数在<metal_common>头文件中&#xff0c;T为scalar或vector的浮点类型。如下表所示&#xf…

公司最大的内卷,是OKR驱动

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 去年5月&#xff0c;以善用OKR独步江湖的Google公司宣布将对 Google 员工的绩效管理方式进行改革——Google 开始使用一种名为 GRAD 的新绩效评估流程。据了解&#xff0c;这一调整主要是基于员工对谷…

集权设施管理-AD域安全策略(二)

活动目录&#xff08;AD&#xff09;凭借其独特管理优势&#xff0c;从众多企业管理服务中脱颖而出&#xff0c;成为内网管理中的佼佼者。采用活动目录来管理的内网&#xff0c;称为AD域。 了解AD域&#xff0c;有助于企业员工更好地与其它部门协作&#xff0c;同时提高安全意…

算法练习5:二进制字符串前缀一致的次数

给你一个长度为 n 、下标从 1 开始的二进制字符串&#xff0c;所有位最开始都是 0 。我们会按步翻转该二进制字符串的所有位&#xff08;即&#xff0c;将 0 变为 1&#xff09;。 给你一个下标从 1 开始的整数数组 flips &#xff0c;其中 flips[i] 表示对应下标 i 的位将会在…

docker容器 - 卷(volume)- 挂载

目录 参考文档&#xff1a;Volumes | Docker Documentation 什么是卷&#xff08;volume&#xff09;&#xff1f; 什么是挂载&#xff0c;它的作用是什么&#xff1f; 一台机器里的多个容器之间共享数据&#xff08;使用挂载&#xff09; 首先我们可以使用最简单的docker …

预训练、微调和上下文学习

最近语言模型在自然语言理解和生成方面取得了显著进展。这些模型通过预训练、微调和上下文学习的组合来学习。在本文中将深入研究这三种主要方法&#xff0c;了解它们之间的差异&#xff0c;并探讨它们如何有助于语言模型的学习过程。 预训练 预训练&#xff08;Pre-training&…

RadEx Pro处理电火花数据操作步骤(上)

最近单位采集了很多的电火花测线&#xff0c;同事在使用GeoSuite AllWorks 2022R1处理这些测线的时候&#xff0c;发现二次波对地层辨识和划分干扰比较严重。GeoSuite AllWorks 压制二次波的能力有限&#xff0c;有人推荐我们试一试地震处理软件RadEx Pro。 两个中文文档“RadE…

7--Gradle进阶 - settings.gradle的文件说明

7--Gradle进阶 - settings.gradle的文件说明 前言 介绍 settings.gradle 文件之前&#xff0c;先来说明一下&#xff0c;settings.gradle 主要是用来多模块工程使用的。 所以我们先来创建一个多模块的工程。 多模块工程创建 1. 创建 root 工程 1.1 配置本地 Gradle 1.2 配置依赖…

一口总结了金九银十(P5-P7 级)1000 多道 Java 面试题,20+ 大厂必考点及 Java 面试框架知识点

Java 面试 “金九银十”这个字眼对于程序员应该是再熟悉不过的了&#xff0c;每年的金九银十都会有很多程序员找工作、跳槽等一系列的安排。说实话&#xff0c;面试中 7 分靠能力&#xff0c;3 分靠技能&#xff1b;在刚开始的时候介绍项目都是技能中的重中之重&#xff0c;它…

微服务架构基础--第2章初识SpringBoot

第2章初识SpringBoot 一.预习笔记 1.SpringBoot的定义&#xff1a; SpringBoot是由Pivotal团队提供的一个全新框架&#xff0c;是为了简化Spring应用的初始搭建过程和开发过程。 2.SpringBoot的优点 1&#xff09;可快速的构建独立Spring应用程序 2&#xff09;内嵌Servle…

​DMBOK知识梳理for CDGA/CDGP——第六章 数据存储与操作(附常考知识点)

第六章 数据存储与操作 第六章在CDGA|CDGP考试中的分值占比较少&#xff0c;知识点比较密集&#xff0c;主要考点包括&#xff1a;数据存储与操作的定义、目标、数据库管理员&#xff08;DBA&#xff09;的角色定位及类型、数据处理的类型ACID和BASE的区别、数据库环境、活动、…

探索工业智能检测,基于轻量级YOLOv5s开发构建焊接缺陷检测识别系统

前面也有讲过将智能模型应用和工业等领域结合起来是有不错市场前景的&#xff0c;比如&#xff1a;布匹瑕疵检测、瓷砖瑕疵检测、PCB缺陷检测等等&#xff0c;在工业领域内也有很多可为的方向&#xff0c;本文的核心目的就是想要基于目标检测模型来开发构建焊接缺陷检测模型&am…

基于opencv测量图片中物体的尺寸(matlab实现)

1、引言 问题重述 已知书本上右下角放一枚一元人民币&#xff08;直径2.5厘米&#xff09;&#xff0c;请利用计算机视觉技术预测图片中目标的实际尺寸。 1.预测图片中书本的长与宽&#xff08;单位&#xff1a;厘米&#xff09;。 2.预测书本右上方用铅笔画的圆圈的外圆直径…

Cocos Creator:AR 交互

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 Cocos Creator&#xff1a;AR 交互 3D工具集&#xff1a; NSDT简石数字孪生 AR 交互 AR 交互主要由 cc.ScreenTouchInteractor 组件驱动&#xff0c;该组件将触摸事件转换为点击、拖拽和捏合等手势&#xff0c;交互器将这…

SciencePub学术 | 信号处理类重点SCIEI征稿中

SciencePub学术 刊源推荐: 信号处理类重点SCI&EI征稿中&#xff01;影响因子高&#xff0c;自引率低&#xff0c;对国人非常友好。信息如下&#xff0c;录满为止&#xff1a; 一、期刊概况&#xff1a; 信号处理类重点SCI&EI &#x1f4cc;【期刊简介】IF&#xff1…

RTK 定位回传数据转内网(局域网)mqtt协议--- 格林恩德 CR102 RTK 针对无人机巡检应用

先简单介绍一下CR102 格林RTK高精度设备&#xff0c;CR102接收机&#xff0c;集成高精度模组与4G&#xff0c; WIFI/蓝牙通信模组&#xff1b;双天线定位定向&#xff0c; 同时内置惯导&#xff0c; 输出加速度和姿态信息。支持4G/WIFI/蓝牙无线传输、 LAN网口传输&#xff1b;…

案例研究|中国矿业大学基于JumpServer构建运维安全体系

中国矿业大学是教育部直属的全国重点高校&#xff0c;是教育部、应急管理部与江苏省人民政府共建高校&#xff0c;先后进入国家“211工程”“985优势学科创新平台项目”和国家“双一流”建设高校行列&#xff0c;学校现坐落于素有“五省通衢”之称的国家历史文化名城——江苏省…

Java实训第七天——2023.6.13

文章目录 一、用Visual Studio Code写一个计算器二、同一个js被多个html引用三、js操作css四、DOM对象属性的操作案例五、js解析json 一、用Visual Studio Code写一个计算器 功能&#xff1a;实现简单的加减乘除 <!DOCTYPE html> <html lang"en"> <…

如何录制声音?推荐这2款电脑录音软件!

案例&#xff1a;怎么录制电脑上的声音&#xff1f;在电脑上怎么录制自己的声音&#xff1f;有没有小伙伴知道操作的步骤。 【我想录制语音会议&#xff0c;还想录制自己的歌声&#xff0c;在电脑上如何录制声音&#xff1f;求一个简单易懂的教程&#xff0c;在线等&#xff0…